简析IAST—Agent篇 | 信息安全

news2024/11/15 10:14:05

一、IAST简单介绍

IAST(Interactive Application Security Testing)交互式应用程序安全测试,通过服务端部署Agent探针,流量代理/VPN或主机系统软件等方式,监控Web应用程序运行时函数执行并与扫描器实时交互,高效、精准的安全漏洞,是一种运行时灰盒安全测试技术。在分析并评估了业界主流的几家IAST产品后,发现IAST一般可以提供agent插桩检测、流量代理、流量信使等几种漏洞检测模式,其中除agent插桩模式外,其余几种检测模式与DAST被动漏洞扫描的原理一致,所以本篇文章我们主要分析agent插桩模式。

agent插桩的检测模式,一般分为主动和被动模式。那怎么理解agent插桩模式呢?我们结合JavaAgent技术来做解释。-javaagent是java命令的一个参数,该参数可以指定一个jar包,该jar包内实现了一个premain()方法,在执行正常程序的main()方法会先运行这个premain方法,是一个JVM层做功能增强的机制。说到java的功能增强,可以想到动态代理、Spring AOP、拦截器等技术,这些方式主要是在函数执行前后做一些增强逻辑,而JavaAgent可以直接获取到类加载前的字节码,再结合一些字节码修改技术,从而通过修改字节码来增强函数功能。通过这种方式,我们可以获取类、方法的一些信息,比如类名、方法名、参数、返回值等,同时也可以做一些拦截操作。所以,这项技术通常被用于实现调用链监控、日志采集等组件,而在安全方向的应用,IAST和RASP则是典型例子。

对于IAST来说,agent的检测模式通常被分为被动模式和主动检测模式。在被动模式下,agent可以动态获取一次请求的调用链、数据流等信息,基于获取的信息,IAST可以做一些基于污点追踪的白盒分析,从而检测该次请求的调用链中是否存在漏洞;在主动模式下,agent会hook一些危险函数,当一次请求触发到这些危险函数后,agent会将该次请求发送给IAST server端,并使用DAST能力发送攻击payload做主动验证。基于这些特性,IAST非常适合融入到DevOps的测试环节,在业务测试完成正常功能逻辑测试工作的同时,无感知的进行一些安全漏洞的检测。

  • IAST被动检测模式

    9c9579f6fabe74a831d59876fbd5d1d9.png

  • IAST主动检测模式

5f31e4d6bc8ac1f51f2abb22bfff64dd.png

这里我们主要研究一下IAST被动检测模式的细节,该检测模式也是IAST首推的检测模式。在上文提到,在被动检测模式下,agent主要用来做Web应用程序的数据采集并传至分析引擎,分析引擎做白盒分析。接下来,我们就实际的去实现一个agent,对一次请求做调用链数据采集。

还是以Java为例,实现一个agent需要掌握以下几种技术:

  • JavaAgent技术。

  • 字节码修改技术。主流的字节码修改工具有javassist、Byte Buddy和ASM等,其中javassit的使用比较简单,ASM则较为复杂且需要对.class文件结构、变量表等底层知识有一定的理解,但是其性能更优。我们这里只是写一个简单讲解一下agent的工作逻辑,所以我们选择使用javassist。

  • JVM类加载机制。

  • ThreadLocal

接下来,我们来简单写几个例子介绍一下这几项技术。

二、JavaAgent

前文已经基本介绍了JavaAgent这项技术,而实际实现一个JavaAgent需要以下几个步骤:

  • 定义一个MANIFEST.MF文件,必须包含Premain-Class选项,通常也会加入Can-Redfine-Classes和Can-Restransform-Classes选项。

  • 创建一个Premain-Class指定的类,类中包含premain方法,方法逻辑由用户自己去编写。

  • 将premain的类和MANIFEST.MF文件打成jar包。

  • 使用-javaagent参数,启动要代理的方法。

在执行以上步骤后,JVM会先执行premain()方法,大部分类加载前都通过该方法。

通过上面的步骤,我们来接下来实现一个简单的JavaAgent

2.1 包含premain方法的agent类

先给出一个例子:

df0749af6240b8b6b59f30b00d14d52b.png

解释一下以上代码,在类加载时,JVM会先执行premain()方法,方法中的inst.addTransformer(new DefinTransformer(), true)是添加一个ClassFileTransformer接口的实现类, 该类实现一个transform()方法,该方法中我们可以写一些自己的逻辑。可以看到transform的入参有被加载类的类加载器、类名、字节码等信息,返回类型则是byte[],这样我们可以返回修改后的字节码,之后JVM加载的就是我们修改过后的字节码了,这里我们简单打印一下加载中的类名。

现在我们还缺少一个MANIFEST.MF文件,使用maven插件可以在打包时自动生成。

7680a3ad62be1300de27fbb109cc9279.png

Premain-Class和Agent-Class写agent类型的全类名即可,如果需要添加其他配置,可在<manifestEntries>中添加对应标签和值,这里不再多做介绍。

2.2 运行agent

写一个用于测试的类并编译成.class。

7639c9d15e71681f6256f9eadd63350a.png 

使用-javaagent参数执行agent包并运行:

ca82a1246c6871d78839264c350725d1.png

输出结果如下:

51ff3b2155b471587e24821ea0ba12aa.png

可以看到不止我们自己编写的测试类,有很多jdk中的类也被打印出来了,可见JavaAgent功能还是比较强大的。

三、Javassist

Javassist可以实现动态创建类、添加类的属性和方法,修改类的方法等操作。这里我们不需要做添加方法、属性等操作,只需要获取类名、方法名、入参、出餐等信息,所以这里仅仅叙述如何获取这些信息,至于其他多的功能,可参考:https://bugstack.cn/md/bytecode/javassist/2020-04-19-%E5%AD%97%E8%8A%82%E7%A0%81%E7%BC%96%E7%A8%8B%EF%BC%8CJavassist%E7%AF%87%E4%B8%80%E3%80%8A%E5%9F%BA%E4%BA%8Ejavassist%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%A1%88%E4%BE%8Bhelloworld%E3%80%8B.html

3.1 信息获取

获取类

0d8b948841348359616592c79b0c5db4.png

通过类名获取类的信息,也包括类里面一些其他获取属性的操作,比如:ctClass.getSimpleName()、ctClass.getAnnotations() 等。

获取方法

44d8a3f790716f3b358a41810668fdab.png

通过 getDeclaredMethod 获取方法的 CtMethod 的内容。之后就可以获取方法的名称等信息。

方法信息

e2287288e4e28cc5a690f632ce521d6f.png

MethodInfo 中包括了方法的信息;名称、类型等内容。

方法类型

246dfcec0c136c6bef615592ab562bb1.png

通过 methodInfo.getAccessFlags() 获取方法的标识,判断方法是否为静态方法。因为静态方法会影响后续的参数名称获取,静态方法第一个参数是 this ,需要排除。

入参信息

2e41391054503f4320a6ed65036e5dae.png 

返回信息

961ee75ead918658dcb72f7c1cd74028.png

修改方法

ed0809c6a2b656262ab7b547fd0c9d48.png

四、Agent实现

4.1 写在最前

在实现一个agent前还有几个问题需要注意:

agent隔离加载

因为我们现在需要实现具有一定复杂逻辑的agent,所以我们不可避免的需要引入一些其他jar包做功能实现,而如果我们的agent与正常应用引入了同一个jar包的不同版本,这将发生jar包冲突,对正常应用产生未知影响。

要解决这个问题,我们需要自定义类加载器来加载agent中的类,因为在java中通过不同的类加载器加载同一个类是不一样的,一个类只能引用同一类加载器加载的类及其父加载器加载的类,更多的细节大家也可以去详细了解一下Java的类加载机制,java的很多框架如spring都有实现自己的类加载器实现隔离加载来解决冲突的问题。

所以我们现在需要将这个agent分为两个jar包,一个是agent包,另一个core包。agent包实现一个premain()方法、Tranformer类等,并且使用自定义类加载器加载core包,而core包是我们编写的修改字节码的逻辑。但这样还会有一个问题,我们修改后的正常代码,需要调用core包中的方法,而core包是自定义类加载器加载的,正好代码的类一般是应用类加载器加载的,所以正常代码这时候无法调用core中的方法。所以,还需要将正常代码调用的方法单独拆分出一个jar包,该包使用BootstrapClassLoader加载,该类加载器是类加载器的顶层,其加载的类可以被所有的类加载器加载的类引用,我们将这个jar命名为spy。

标识一次请求的调用链

在解决了一些类加载上的问题后,我们还有一个问题需要解决。因为我们需要对不同请求采集不同的调用链,那么如何当一次请求触发到一个方法后,我们如何标识这次方法调用是属于哪次请求呢?这也是我们方法调用能否形成链路的关键。

这里我们借用ThreadLocal。ThreadLocal叫做线程变量,即ThreadLocal中填入的变量只属于当前线程,我们知道不同的请求实际对应不同的线程,那用独属于当前线程的变量标识一次请求是最合适不过了。

ThreadLocal的使用也比较简单,如下:

e1291f018c4b125cb6db03d93cbbd9bf.png

4.2 Agent包实现

首先,我们的项目分为三部分,agent、core和spy。

7f508ece1b57b592680858ff8d098eed.png

接下来,我们来实现一下agent包里的逻辑,agent主要实现两个类,一个是自定义的ClassLoader,一个是agent入口类。ClassLoader主要重写loadclass方法主要是注意一些jdk本身的类还是需要正常加载,第二就是spy包的类需要使用BootstrapClassLoader加载,其他的就不再多说。agent类实现permain()方法,主要是分别用不同的类加载器加载core和spy两个jar包,以及为instrumentation添加Transformer类。具体如下:

c8877d90ed254c4c48274f9526b1b76a.png

MyClassLoader代码如下:

4301422c42c1d79fd36a49145dee8f96.png

pom.xml 

44c4b391c8d6d09ba59f180817b14918.png

842fbb3b59fda6231b107d8dd0ff9171.png

4.3 spy包实现

spy包主要有两个类,一个是AbstractAspect抽象类,core包里会有一个Aspect继承它,一个是SpyAPI类,它主要是装载Aspect类,为正常程序提供接口从而调用我们Aspect类实现的方法。

SpyAPI类如下:

4b74c96b8caf4d2e185c2d3809b06470.png

其中,setSpy()实现装载core包中的Aspect类,atEnter()、atExit()、atExceptionExit()分别在程序进入、退出、异常的时候做不同的方法逻辑修改。

4.4 core包实现

因为core包中的类比较多,我们就挑几个比较重要类和方法讲解。首先是我们自己实现的Transformer中的transform()方法,这里我们可以对需要修改字节码的类做下过滤,如果修改的类比较多的话,项目启动会极其缓慢,且容易发生栈溢出问题,这是一项需要长期优化的工作,所以我们这里选择只对应用程序本身的类进行修改,简单看下效果即可。transform()方法代码如下:

748de75125bcfe0c630851d1092e2e0d.png

其中BaseAdaptor也是一个抽象类,我们实际还有一个NormalAdaptor的实现类,这里的逻辑如果有一些设计模式基础的话会更容易理解一些。Adaptor类中主要实现一个modifyCode()方法,我们具体来看一下。

e22c9757b5e8a7c826fb620e7c4abdb8.png

这里大部分是javassist的代码,需要注意的有两个地方:第一个是classPool.appendClassPath(spyJarPath);如果没有这行代码的话,会有nopoint错误;第二个是classPool.appendClassPath(new LoaderClassPath(loader));,通常我们插入agent的程序是一个Web,一般都有类似于spring之类的框架,上文我们也提到这些框架也会有自己的类加载器实现类隔离加载,根据双亲委派原则,ClassPool无法获取这些类,所以需要把当前ClassLoader加载的类添加到ClassPool中。接下来到addMethodAspect()方法:

8bbfc8c33a3419e078414f8fc25ccca4.png

6be9e4b8987e67f16d258acf94271314.png

可以看到,该方法主要是执行了一些方法信息等获取,并分别在方法进入、退出、异常的时候做对应的修改。

调用SpyAPI的方法,其实就是调用我们实现的NormalAspect中的方法。这里我们只看一下atEnter()里面的逻辑, 这三个方法是差不多的。

626b2df473f4e3822cac989d7493101a.png

这里大部分逻辑是做一些参数封装,并打印输出。需要注意的是这里用到了ThreadLocal,需要在我们开始记录调用链的入口处做拦截,生成一个TRANSACTION_ID并将其附加到当前线程上,这个TRANSACTION_ID将在atExit()方法做清除。

到这里,我们实现的主要逻辑就已经介绍完毕了。我们为一个springboot项目插桩,简单看下效果。

指定-javaagent参数启动

java -javaagent:./agent-1.0-SNAPSHOT-jar-with-dependencies.jar -jar secexample-1.0.jar

请求效果

6676eae18d65d011279759435c100a11.png

存在parameterList字段的记录是进入方法的信息,存在returnType字段的记录是退出方法的信息,我们看到不同请求的traceId也是不一致的,由此可以标识不同请求的调用链。这里由于我们修改的类比较少,形成的调用链比较简单,如果确定好合适的需要修改的类后,则可以看到更详细的调用链条。

五、 写在最后

到这里,本篇文章分析的内容就到尾声了,写这个小demo的时候其实也踩到了很多坑,个别问题花了大量时间解决或是到现在也没能解决,主要是以下几个问题:

  1. 框架适配,不同的框架有不同的入口,需要寻找框架合适的入口做traceId的生成和清除,也是一项长期且复杂的工作。

  2. 分布式追踪,有些服务是使用分布式、微服务的这种架构,这时候需要对分布式框架的出入口做对应的修改,将当前的traceId到框架请求中,这样调用链才能追踪下去。

  3. javassist的一些问题,javassist还是过于简单了一些,某些方法的bug官方到现在也没有修改,比如获取参数信息attribute.variableName(i),经常会有数组越界的问题,在后续版本更新了attribute.variableNameByIndex(i)方法后,这种情况依然偶有发生,这跟本地变量表有关系,javassist显然是有些情况没能考虑到,还是ASM更靠谱一些。

  4. 目前实现的这种traceId标记一次请求调用链还是有一些问题的,如果在一次请求中的代码也启动子线程就会导致一部分调用链的缺失。对于new Thread()创建线程,我们可以通过InheritableThreadLocal将traceId拷贝到子线程中。但如果使用线程池就比较麻烦了,需要去修改线程池相关的类和方法的字节码。

IAST agent需要实现的更多功能,大家可以参考火线洞态IAST,其在github上开源了一个java版本的agent,还是具有比较良好的借鉴意义的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/674208.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode 面试题 16.19. 水域大小

LeetCode 面试题 16.19. 水域大小 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/group-anagrams/description/ 博主Github&#xff1a;https://github.com/GDUT-Rp/LeetCode 题目&#xff1a; 你有一个用于表示一片…

C++初阶之初识C++

初识C 1.什么是C2.C的发展史2.1 历史渊源2.2 名称由来2.3 C标准 3.C的重要性3.1 语言的使用广泛度3.2 工作领域 4.如何学习C5.结语 1.什么是C C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的问题&#xff0c;规模较大的 程序&#xff0c;需要高度…

【k8s系列】一分钟搭建MicroK8s Dashboard

本文基于上一篇文章的内容进行Dashboard搭建&#xff0c;如果没有看过上一篇的同学请先查阅上一篇文章 k8s系列】使用MicroK8s 5分钟搭建k8s集群含踩坑经验 使用MicroK8s搭建Dashboard很简单&#xff0c;只需要在Master节点按照以下几步操作 1.启用Dashboard插件 microk8s en…

“Vue3+Vite打包后,白屏沉默,重启重试无果,我该如何解决?”

每次最后打包总是会或多或少出现一些问题&#xff0c;昨天打包项目完之后 直接点击dist中的index.html去看看有没有什么发题&#xff0c;一打开奇怪的事发生了&#xff0c;居然是空白&#xff1b;但是在vscode中右键Open with Live Server一看项目没啥问题&#xff0c;但是部署…

下载安装Python解释器和环境变量配置

一、 python解释器下载 1.百度python官网并打开 官网网址&#xff1a;https://www.python.org/ 在这里插入图片描述 2.选择DownLoads&#xff0c;Windows并打开 3.在下图页面选择你需要的版本下载即可&#xff08;本文以python 3.10.4版本演示&#xff09; 在这里插入图片描述 …

C++笔记之extern关键字

C笔记之extern关键字 code review! 文章目录 C笔记之extern关键字0.前言1.extern是C语言的关键字还是C中的关键字&#xff1f;2.extern关键字和全局变量3.ChatGpt讲述extern的用法4.extern一般用法4.1.在本模块中使用4.2.跨模块中使用 5.标准定义使用extern关键字的步骤7.ext…

Nginx 访问日志中有 Get 别的网站的请求是什么原因?

&#x1f482; 个人网站:【海拥】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 目录 前言理解 Nginx 访问日…

父亲的打,最让人想念。

11年前的端午节&#xff0c;父亲就走了。 父亲十五岁就外出打工&#xff0c;从小出去打工&#xff0c;吃了没有文化的苦&#xff0c;父亲从小教育我要好好读书&#xff0c;可我从来听不进。 所以我经常挨打。 老家在湖南的一个农村&#xff0c;我父亲十几岁就南下打工&#xff…

[进阶]网络通信:TCP通信,一发一收,多发多收

TCP通信 特点&#xff1a;面向连接、可靠通信。通信双方事先会采用 “三次握手〞 方式建立可靠连接&#xff0c;实现端到端的通信&#xff1b;底层能保证数据成功传给服务端。Java提供了一个java.net.socket类来实现TCP通信。 TCP通信——客户端开发 客户端程序就是通过java.…

基于OpenCV-车辆检测项目(简易版)

车辆检测 1.项目介绍2. 读取一段视频3.通过形态学处理识别车辆4.描画轮廓5. 车辆计数并显示 本项目使用的视频地址链接 1.项目介绍 对一个视频进行车辆数量的检测&#xff0c;用到的知识有视频的读取&#xff0c;滤波器&#xff0c;形态学&#xff0c;添加直线、文本&#xff…

黑马头条2

文章目录 前言一、接口工具1.1 postman1.2 swagger1.3 knife4j 二、 网关一、基本搭建二、全局过滤器jwt 三、 前端集成导入前端工程项目安装nginx测试 &#x1f315;博客x主页&#xff1a;己不由心王道长&#x1f315;! &#x1f30e;文章说明&#xff1a;黑马头条开发&#x…

一站式数据可观测性平台 Datavines 正式开源啦

Datavines是一站式开源数据可观测性平台&#xff0c;提供元数据管理、数据概览报告、数据质量管理&#xff0c;数据分布查询、数据趋势洞察等核心能力&#xff0c;致力于帮助用户全面地了解和掌管数据&#xff0c;让您做到心中有数&#xff0c;目前作为 Datavane 开源组织的重点…

Java ForkJoin 简介和应用

Java 并行框架 Fork Join 一.Fork Join 简介1.框架说明2.任务说明 二.应用示例1.RecursiveTask分组示例分组求和 2.RecursiveAction3.CountedCompleter 三.ForkJoin 实践代码测试1.测试用 Excel 文件2.读取结果 一.Fork Join 简介 1.框架说明 ForkJoinPool 继承自 AbstractEx…

【Java-SpringBoot+Vue+MySql】Day4-VUE框架使用

一、VUE入门 1、环境准备 2、预备知识 3、实战演练 vue官网 Vue.js - 渐进式 JavaScript 框架 | Vue.js 基础语法&#xff0c;vue2和vue3区别不大&#xff0c;但是后面路由会有很大区别。 前期基础语法&#xff0c;我们通过链接的方式使用vue&#xff0c;后面会用npm进行安装…

Transformer-XL模型简单介绍

目录 一、前言 二、整体概要 三、细节描述 3.1 状态复用的块级别循环 3.2 相对位置编码 四、论文链接 一、前言 以自注意力机制为核心的 Transformer 模型是各种预训练语言模型中的主要组成部分。自注意力机制能够构建序列中各个元素之间的上下文关联程度&#xff0c;挖掘…

java 2023秒杀项目 day(1) 面经

java 2023杀项目 day(1) 面经 一、秒杀项目1.1 如何设计秒杀系统1.2 数据库 二、业务2.1 登录2.2.1 密码加密 2.2.2 密码参数校验2.2.3 分布式session2.2.3.1 解决方案 2.2.4 参数解析器 2.3 异常处理2.3.1 ControllerAdvicerExceptionHandler 2.4 秒杀2.4.1 逻辑2.4.1 秒杀前判…

图像处理——以支票识别为例

用到环境 1、pycharm community edition 2022.3.2 2、Python 3.10 后续应该会在资源上传项目&#xff0c;需要的话可以私信我。 流程 图1 扩展实验“金额识别”流程图 正文 导入 cv2、pytesseract、re 和 locale 模块。 使用 cv2.imread() 函数加载名为 cheque.jpg 的支票图像…

ROS:结构

目录 前言一、设计者二、维护者三、系统架构四、ROS自身结构 前言 从不同的角度&#xff0c;对ROS架构的描述也是不同的&#xff0c;一般我们可以从设计者、维护者、系统结构与自身结构4个角度来描述ROS结构: 一、设计者 ROS设计者将ROS描述为“ROS Plumbing Tools Capab…

数仓工具Hive 概述

Hive Hive简介Hive架构HiveSQL语法不同之处建表语句查询语句 Hive查看执行计划Hive文件格式 Hive简介 Hive是由Facebook开源&#xff0c;基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张表&#xff0c;并提供类SQL查询功能。 通过Hive可以将mapred…

数字IC前端学习笔记:仲裁轮询(三)

相关文章 数字IC前端学习笔记&#xff1a;LSFR&#xff08;线性反馈移位寄存器&#xff09; 数字IC前端学习笔记&#xff1a;跨时钟域信号同步 数字IC前端学习笔记&#xff1a;信号同步和边沿检测 数字IC前端学习笔记&#xff1a;锁存器Latch的综合 数字IC前端学习笔记&am…