xxx代码审计面试

自我介绍

## 讲一下你怎么审计项目

我从开发那里拿到 Java 源码后,然后采用 ’ 工具辅助 + 人工精查 ’ 的分层审计策略。首先通过静态扫描工具发现常见漏洞(如 SQL 注入、反序列化),然后重点审计核心业务流程(如支付、认证),同时关注依赖组件的安全性。对于发现的每个漏洞,我会进行复现、评估风险等级,并提供具体的修复建议。例如在某项目中,我们通过追踪数据流发现了一处 XXE 漏洞,最终通过配置 XML 解析器的安全特性解决了问题。此外,我认为审计不应局限于发现问题,还应推动团队建立持续安全的机制,如安全测试自动化和开发规范培训。

面试官:那你能结合项目,讲你审计过的漏洞

在我之前的项目中审计出过反序列化 RCE 漏洞。当时是通过工具扫描结合人工分析,在用户个人资料模块发现ObjectInputStream,我查看利用条件依赖 Commons Collections 3.1,当时依赖的配置版本低于这个版本。修复方案:

  1. 替换为 JSON 序列化
  2. 添加ObjectInputFilter白名单
  3. 升级依赖至安全版本

Sql注入你是怎么审计的

我会用工具辅助,人工代码审查、安全测试相结合的系统化方法

先使用动态工具扫描,使用 SQLMap、OWASP ZAP 等工具对 Web 应用进行自动化扫描。SQLMap 可通过参数注入测试识别可能存在漏洞的 URL 或表单,ZAP 则能对整个 Web 应用进行爬取并检测 SQL 注入风险。但需注意自动化工具可能存在误报,需结合人工验证。

通过 Burp Suite 抓包分析 HTTP 请求与响应,手动修改参数内容(如添加单引号、尝试闭合 SQL 语句),观察数据库返回的错误信息(如报错信息包含数据库表结构、字段名),判断是否存在注入点。若返回统一错误页面,则需结合盲注技术(布尔盲注、时间盲注)进行验证。

人工对代码静态分析。先检查接收外部输入的接口(如 HTTP 请求参数、数据库存储过程的传入值),关注用户可控数据与 SQL 语句拼接的位置,例如 Java 中的Statement对象直接拼接参数、PHP 中未处理的$_GET变量直接用于 SQL 查询。

检查是否使用预编译语句(如 Java 的PreparedStatement、Python 的psycopg2模块)或 ORM 框架(Hibernate、Django ORM),确认其参数绑定机制是否正确。同时,查看是否存在自定义 SQL 拼接函数,若有则需审查其过滤规则是否覆盖常见攻击字符(如单引号、分号、注释符)。

对部署中间件进行检查,检查数据库权限配置,确保应用使用的数据库账户权限最小化(如仅授予必要的查询、插入权限),避免使用root等超级管理员账户。同时,查看是否启用数据库防护功能,如 MySQL 的sql_mode是否设置为严格模式,防止恶意 SQL 执行。

审查数据库日志(如 MySQL 的慢查询日志、错误日志),查找异常 SQL 语句,分析是否存在高频错误、异常的 SQL 结构或非预期的查询操作。

最后进行业务逻辑测试,针对业务流程设计特殊测试用例,例如在登录注册模块尝试注入 SQL 语句绕过身份验证,或在搜索框中构造跨表查询语句获取敏感数据。

数据类型验证:验证应用是否对输入数据类型进行严格校验,例如仅允许数字的字段是否拒绝字符串输入,防止通过类型绕过注入防护。

若发现未使用预编译的 SQL 拼接漏洞,会建议改用参数化查询,并提供具体的代码修改示例;对于复杂的业务逻辑漏洞,会结合业务场景设计针对性的防护方案,确保审计结果可落地。

Cc链了解过吗,讲一下cc1吧

CC1 链(CommonsCollections1 链)是 Java 反序列化漏洞中最经典的利用链之一,它利用 Apache Commons Collections 库的漏洞,允许攻击者通过反序列化不可信数据执行任意代码。

CC1 链基于 Java 反序列化的安全缺陷:当应用程序反序列化来自不可信源的数据时,攻击者可构造恶意序列化对象,触发一系列类的readObject()方法,最终通过反射调用Runtime.getRuntime().exec()执行任意命令。 关键组件:Apache Commons Collections 库(版本 3.1-3.4,高版本修复了部分漏洞)。

它的核心组件与调用链

CC1 链的核心是通过Transformer 链实现反射调用,主要涉及以下类:

  1. Transformer 接口:定义transform(Object input)方法,用于转换对象。
  2. ChainedTransformer:组合多个 Transformer,按顺序执行转换。
  3. ConstantTransformer:返回固定值(如Runtime.class)。
  4. InvokerTransformer:通过反射调用任意方法(如getMethod()、invoke())。
  5. AnnotationInvocationHandler(JDK 类):重写了readObject(),触发 Transformer 链。

简化的调用路径: 反序列化触发readObject() → AnnotationInvocationHandler.readObject() → LazyMap.get() → ChainedTransformer.transform() → Runtime.exec()。

利用条件

  1. 依赖版本:Commons Collections 3.1-3.4(需在类路径中)。
  2. 反序列化入口:存在接收用户输入并进行反序列化的代码(如 RMI、HTTP 接口)。
  3. JDK 版本限制:原生 CC1 链仅在JDK 8u71 及以下版本有效,高版本通过修复AnnotationInvocationHandler的readObject()方法阻止了链式调用。

防御措施

  1. 升级依赖:使用 Commons Collections 4.0 + 或修复版本,避免已知漏洞。
  2. JDK 版本控制:升级到 JDK 8u71 以上,并配置com.sun.jndi.rmi.object.trustURLCodebase=false。
  3. 输入过滤:拒绝反序列化不可信数据,或使用白名单类过滤器(如 Java 9 + 的ObjectInputFilter)。
  4. 运行时防护:使用安全管理器(SecurityManager)限制反射权限,或部署 Web 应用防火墙(WAF)拦截特征性 Payload。

面试加分项

  • 变种与绕过:高版本 JDK 可通过CC6/CC7 链绕过限制,利用不同类组合实现相同效果。
  • 实战场景:结合 RMI/HTTP 反序列化漏洞,通过攻击未授权的 RMI 接口执行命令。
  • 工具推荐:使用ysoserial工具一键生成 CC1 Payload(java -jar ysoserial.jar CommonsCollections1 “calc.exe”)。

反序列化的原理

反序列化是将序列化后的数据恢复为原始对象的过程,其核心原理主要包含以下几个关键步骤:

首先是解析数据格式。序列化后的数据会以特定格式存在,比如常见的 JSON、XML,还有 Protocol Buffers 等二进制格式。在反序列化时,第一步就是识别数据格式,并依据对应格式的语法规则进行解析。以 JSON 为例,它采用键值对形式描述对象,反序列化时就要按照 JSON 的语法规范,将这些文本数据转化为程序能够理解和处理的内部表示形式。

接着是创建对象。解析完数据格式后,程序会根据获取到的对象类型等信息,在内存中实例化对象。在 Java 中,会调用对应类的构造函数;而在一些使用设计模式的场景下,可能会通过工厂方法来创建对象实例,以此在内存中构建出符合要求的对象雏形。

然后是设置对象属性。程序将解析出的数据值,按照对象的类定义和序列化数据中的属性对应关系,逐个赋给新创建对象的相应属性。比如从 JSON 数据{“name”: “Alice”, “age”: 25} 反序列化时,就会把“Alice“赋值给Person对象的name属性,25赋值给age属性,从而完整构建对象的状态。

最后是重建对象关系。当对象之间存在关联关系,像引用关系、继承关系等时,反序列化还需恢复这些关系。例如,若Book类对象包含Author类对象的引用,反序列化Book对象时,不仅要还原Book自身属性,还要反序列化Author对象,并正确建立两者之间的引用关联 ,让对象之间的关系与序列化之前保持一致。

通过以上步骤,反序列化实现了从存储或传输的序列化数据,到内存中可用对象的转换,方便程序对数据进行后续的逻辑处理、业务操作等。

反序列化漏洞的基本原理

在Java反序列化中,会调用被反序列化的readObject方法,当readObject方法被重写不当时产生漏洞

public class demon {

public static void main(String args[]) throws Exception{

//序列化

//定义myObj对象

MyObject myObj = new MyObject();

​ myObj.name = "hi";

//创建一个包含对象进行反序列化信息的”object”数据文件

ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("object"));

//writeObject()方法将myObj对象写入object文件

​ os.writeObject(myObj);

​ os.close();



//反序列化

//从文件中反序列化obj对象

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object"));

//恢复对象

MyObject objectFromDisk = (MyObject)ois.readObject();

​ System.out.println(objectFromDisk.name);

​ ois.close();

}



static class MyObject implements Serializable {

public String name;

//重写readObject()方法

private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{

//执行默认的readObject()方法

​ in.defaultReadObject();

//执行打开计算器程序命令

​ Runtime.getRuntime().exec("calc.exe");

​ }

}

}

此处重写了readObject方法,执行了 Runtime.getRuntime().exec()

defaultReadObject方法为ObjectInputStream中执行readObject后的默认执行方法

运行流程:

myObj对象序列化进object文件

从object反序列化对象

调用readObject方法

执行Runtime.getRuntime().exec(“calc.exe”);

总体来说讲,面试还是基础的,有些知识点点背的不够充分,好好准备的话还是能过面试的