下午好啊大家
这里是上五休一再上二休七的超级牛(调休版)
先提前祝大家国庆快乐
牛牛这次要给大家介绍一手
“基于代码建模的大型项目代码审计新方法”
⬇️
因为大家更了解 AST,所以我们以 AST 为主语来解释“通用 IR”是什么概念:虽然 Java 的 AST 和 PHP 的 AST 在结构上是不一样的,但是作为基本函数表达,
funcName(actualParam1, actualParam2)
这种语言核心表达是不变的。因为虽然结构整体可能不同,但是 AST 在细节行为上基本是一致的。我们可以新设计一个数据结构,把两种 AST 在一些关键行为上统一用我们的新的数据结构进行表达。这种新的结构就是一种 “中间表达 IR”。
因此,N 种语言有 N 种 AST 结构,如果你基于 AST 编写了对 N 种语言的不同的分析方法,那么每种分析方法对应的结构也是不同的,你就要每一种语言都是适配。但是如果N 种复杂的 AST 结构能变成统一的一种中间表达(可理解为“通用 AST”),那么你就只需要探索这 “唯一的一种中间表达” 的行为分析技术了。
如果我们再把这一种统一的“中间表达”变成数据结构保存到数据库的“表”中,那么都不用新创建什么 CodeQL 之类的语言,或者什么冷门 Datalog 语言,数据库本身的查询语句都可以起到一定的作用。
SELECT edges FROM codes WHERE funcname LIKE 'eval%';
当我们了解完背景知识之后,其实已经掌握了核心思路,相当于你已经知道了目前最正统的“五雷正法”的修炼路径。但是修炼路上困难重重,仍然需要大量的“知识”来指导上述思路的落地时间,越是朴素简单的思路动手起来往往难度越高。
第一站就是了解我们说的N种AST都能统一的中间表达:这是本讲中最核心也可能是最有张力的技术,全称是静态单赋值的中间表达(Static Single Assignment Intermediate Representation)。其实单从定义上来说,SSA 应该是一个十分简单的技术:在程序中保证每个变量仅被赋值一次。但是这一句话其实隐含了非常大的信息量。
大多数时候,我们会默认为 SSA IR 是一个完全陌生的知识面,实际不然,在很多现代编译器中,SSA 几乎可以说无处不在(虽然他们实现都不一样,但是大部分都是符合 SSA 定义,并且以实现 SSA 化为一个“噱头”,事实上如果你的语言或者你的 IR 实现了 SSA 化,至少意味着它相对在技术上是成熟的,具备极强的扩展能力和自证能力):
LLVM 中有两种 SSA 的应用,一种是本身 LLIR 就是一种 SSA 的表现(尽管在内存使用上使用 Load 和 Store 去绕过 SSA 很难表达的指针操作);另一种是对内存块操作是一个独立的 SSA 指令处理,可以非常好地优化内存中未使用的模块(和死代码删除是同样的思路和技术)。
Golang 的官方实现虽然不接如 LLVM,但是它自己包含了一个 SSA IR 系统;
强如 Rustc 的 MIR(Middle-level IR)使用了类似 LLVM 的非严格 SSA 的系统,可以方便的转换成 LLVM IR 中的 alloca 指令,rustc_mir_transform::ssa 模块实现了 MIR 局部完全 SSA 化。
V8 引擎的 TurboFan 使用 SSA 来直接简化 Use-Def 链,保证了每个 TurboFan 中的值只有一个单一来源,并且快速构建“节点之海”。
仅被赋值一次意味着在大部分时候,数据流都是单向的可以追踪的,你可以明确知道一个变量有没有消亡。SSA 可以回答:“此变量 foo 非彼变量 foo。”
我们可以以最简单的一个案例来理解这种变化:
a = 1;
a = 2;
eval(a);
上述代码中,在 SSA 的理解下,a变量的值一开始被赋值为 1,后赋值为 2。然后被eval(a)
执行了。那么我们追踪程序的时候,从 eval 开始寻找数据,从 AST 角度来说,会发现 a 可能有两个值,分别是 1 还是 2。那么我们怎么准确找到 a 对应的是 2 呢?有两个理解:
如果基于AST考虑,那么此时你肯定会想,我需要寻找离eval最近的一个 a 的值,找到了值为 2;
如果基于符号表来考虑,a在赋值的时候被创建过一次,每次赋值都会覆盖之前的,只需要记录赋值的表,再按a这个变量名取出来就好,确定是 2。
这个问题非常简单,但是看起来好像不需要SSA也能做到,那么我们再看下一个案例。
a = 1;
if (conditionB) {
a = 2;
}
eval(a);
上述代码中,在 SSA 的理解下,a
变量的值一开始被赋值为 1,随后经过 IF,如果条件为真,被赋值为 2。然后被 eval(a)
执行了了。那么我们追踪程序的时候,从 eval 开始寻找数据,从 AST 角度来说,会发现 a可能有两个值,分别是 1 还是 2。那么我们怎么准确找到 a 对应的是 2 呢?有两个理解:
如果基于 AST 考虑,那么此时你肯定会想,我需要寻找离
eval
最近的一个 a 的值,但是我们不确定 conditionB 到底是什么,所以无法确定;如果基于符号表来考虑,a 在赋值的时候被创建过一次,每次赋值都会覆盖之前的,只需要记录赋值的表,再按 a 这个变量名取出来就好,但是此时的表是动态的,也无法确定。
那这种情况,我们如何表达这种 “可能性”呢?至少到这里,传统的 AST 表达方式略显匮乏,到了“只能意会”的阶段了。
在讲解上述问题的解法的时候,我们来完整观察一下 SSA 如何解决那个“简单问题”,根据定义来说,一个变量不能被赋值两次,那么上述代码就应该被修改为符合 SSA 格式的才能继续:
a = 1;
a_1 = 2;
eval(a_1);
我们不需要计算距离,也不需要打变量表,就可以直接得到 eval()
执行的结果必定是 a_1
。在对比原来的代码的时候,如果变量名相同,则创建一个原来变量的新版本,用新版本承载新值。
带条件分支的代码,将会被修订为如下版本
a = 1;
if (conditionB) {
a_1 = 2;
}
a_2 = phi(a, a_1);
eval(a_2);
上述代码引入了一个 SSA 的用来表示“数据流合并”的运算符:phi
我们发现,上述的 a 的值其实会有三种情况:
初始值:1
条件语句 TRUE 分支内的值:2
条件语句结束后的值:phi(a, a_1) 即:可能是 1 可能是 2;
因为 eval 执行的位置是条件语句结束后,所以eval的参数一定为 phi(a, a_1)
;
虽然我们无法确定到底是 1 还是 2 但是获得了一个精准的表达。
如果所有的代码都是 SSA 格式的,那么所有数据都会被精确确定;
如果一个数据有多个可能性,SSA 的 Phi 函数可以准确表达它在数据上可能性;
天生可以做死代码优化,无需额外算法(案例);
SSA IR 可以让代码更快收敛,直接得到不动点;
基础设施匮乏,教科书陈旧,现存数据和文章存在大量错误会误导读者,需要参考大量现有的优秀实现和散落在各种地方的知识,并且很多细节需要发挥想象力。
SSA 多被设计为为了优化程序执行,理清数据流的结构,而并不是分析代码行为的结构;
SSA 的编译没有统一的约束和最佳实践,各个语言的 SSA 都不一样,例如:Golang SSA,LLVM SSA就是完全不同的目的,他们的实现和对 SSA 的忠诚度不一样;
内存操作会产生隐性(间接)赋值,SSA 对内存分配的处理需要额外花一些精力维护隐性(间接)和直接赋值之间的关系,会因为“隐性赋值”产生额外代码;(举例可以理解一下:Golang 中的 “指针/引用” 的处理。)
现有的很多高级语言特性甚至一些经典的编程模式对 SSA 的实现有非常大的挑战,如何转换成了非常难的问题:例如有类语言到无类语言的等价变换,闭包也属于间接赋值,处理的时候非常复杂。
| 听起来有点离谱吧?但是你想想汇编也是一样的,语法变化千秋万代,CPU 指令集确实有限个数。
我们用一张图直观表达这个事情的最终结果:
用途 | 说明 | |
调试 | 用于在代码中插入断言,检查某个条件是否为真 | |
SSAOpcodeBasicBlock | 控制流 | 用于表示基本的代码块,通常包含一系列的指令,没有内部的控制流 |
SSAOpcodeBinOp | 计算 | 用于执行二元操作,如加、减、乘、除等 |
SSAOpcodeCall | 函数调用 | 用于调用函数或方法 |
SSAOpcodeConstInst | 常量 | 用于表示常量值 |
SSAOpcodeErrorHandler | 错误处理 | 用于处理异常或错误 |
SSAOpcodeExternLib | 外部库调用 | 用于调用外部库的函数或方法 |
SSAOpcodeIf | 控制流 | 用于执行条件判断 |
SSAOpcodeJump | 控制流 | 用于执行无条件跳转 |
SSAOpcodeLoop | 控制流 | 用于执行循环操作 |
SSAOpcodeMake | 对象创建 | 用于创建新的对象或数据结构 |
SSAOpcodeNext | 控制流 | 用于跳转到下一个指令 |
SSAOpcodePanic | 错误处理 | 用于处理不可恢复的错误情况 |
SSAOpcodeParameter | 函数参数 | 用于表示函数或方法的参数 |
SSAOpcodeFreeValue | 自由变量 | (闭包支持)一般出现在一个函数捕获了外部的变量,并且这个外部变量不是形式参数 |
SSAOpcodeParameterMember | 对象访问 | 用于访问形参的某个成员 |
SSAOpcodePhi | 控制流 | 用于合并来自不同基本块的值 |
用于从错误或异常中恢复 | ||
SSAOpcodeReturn | 函数返回 | |
准则一:降维与扁平化
字段名 | 类型 | 描述 |
id | integer | 主键,自动递增 |
created_at | datetime | 创建时间 |
updated_at | datetime | 更新时间 |
deleted_at | datetime | 删除时间 |
program_name | varchar(255) | 程序名称 |
version | varchar(255) | 版本信息(SCA 用) |
source_code_start_offset | bigint | 源代码开始偏移量 |
source_code_end_offset | bigint | 源代码结束偏移量 |
source_code_hash | varchar(255) | 源代码哈希值 |
opcode | bigint | SSA 操作码 |
opcode_name | varchar(255) | SSA 操作码名称 |
opcode_operator | varchar(255) | 操作符 |
name | varchar(255) | 名称 |
verbose_name | varchar(255) | 详细名称 |
short_verbose_name | varchar(255) | 简短详细名称 |
string | varchar(255) | 字符串 |
current_block | bigint | 当前块 |
current_function | bigint | 当前函数 |
is_function | bool | 是否是函数 |
formal_args | text | 形式参数 |
free_values | text | 自由变量值 |
member_call_args | text | 成员调用参数 |
side_effects | text | 函数包含的副作用 |
is_variadic | bool | 是否是可变参数函数 |
return_codes | text | 返回代码 |
is_external | bool | 是否是外部的 |
code_blocks | text | 代码块 |
enter_block | bigint | 进入块 |
exit_block | bigint | 退出块 |
defer_block | bigint | 延迟执行代码块 |
children_function | text | 子函数 |
parent_function | bigint | 父函数 |
is_block | bool | 是否是块 |
pred_block | text | 前驱块 |
succ_block | text | 后继块 |
phis | text | 块中直接包含的 Phi 指令 |
defs | text | 数据流定义 |
users | text | 数据流中的使用者 |
called_by | text | 调用者 |
is_object | bool | 是否是对象 |
object_members | text | 对象成员 |
is_object_member | bool | 是否是对象成员 |
object_parent | bigint | 对象父级 |
object_key | bigint | 对象键 |
masked_codes | text | 掩码代码 |
is_masked | bool | 是否被掩码 |
variable | text | 变量 |
program_compile_hash | varchar(255) | 程序编译哈希值 |
type_id | integer | 类型 ID |
point | text | 点 |
pointer | text | 指针 |
extra_information | varchar(255) | 额外信息 |
准则二:多模块与懒加载模式
准则三:SSA 字节码可和变量互相对应
可以搜索数据库中关键词对应的指令; 查看某个指令对应的所有函数调用; 通过关键字筛选并过滤指令; 查看某一个指令位置的数据流关系; 把审计出的结果暂存变量; 迭代器执行所有相关指令; 通过指令类型过滤数据等; SCA 与额外文件适配; ...
代码容错:可以针对不完整的代码进行审计;
支持精确搜索,模糊搜索,指定方法搜索;
支持 SSA 格式下的数据流分析;
支持 Phi 指令处理 IF For 循环等控制流程;
支持 OOP 编译成 SSA 格式后的搜索;
支持 Java 注解的追踪与 SSA 实例化,以适应各类注解入口的框架代码;
支持 Use-Def 链的运算符(向上递归寻找定义,向下递归寻找引用)
通用语言架构:支持 Yaklang / Java / PHP(Alpha*) / JavaScript(ES) Alpha*;
自动跨过程,OOP 对象追踪,OOP 内方法跨过程,上下文敏感与函数栈敏感特性,可以支持复杂数据流分析;
编译产物符号化,构建 Sqlite 格式的标准化符号和 IrCode 表,支持中间表达的可视化。
支持跨过程与数据流可视化(根据 SF 分析过程自动生成),支持数据 Dot 格式的分析步骤图和数据流图
索引与资料查阅
OPCODE 名字 | 含义 |
OpPass | 空操作 |
OpEnterStatement | 进入语句 |
OpExitStatement | 退出语句 |
OpDuplicate | 复制栈顶元素 |
OpPushSearchExact | 精确搜索并压栈 |
OpPushSearchGlob | 模糊搜索并压栈 |
OpPushSearchRegexp | 正则搜索并压栈 |
OpRecursiveSearchExact | 递归精确搜索并压栈 |
OpRecursiveSearchGlob | 递归模糊搜索并压栈 |
OpRecursiveSearchRegexp | 递归正则搜索并压栈 |
OpGetCall | 获取函数调用 |
OpGetCallArgs | 获取函数调用参数 |
OpGetAllCallArgs | 获取所有函数调用参数 |
OpGetUsers | 获取使用者 |
OpGetBottomUsers | 获取底层使用者 |
OpGetDefs | 获取定义 |
OpGetTopDefs | 获取顶层定义 |
OpListIndex | 列表索引 |
OpNewRef | 创建引用 |
OpUpdateRef | 更新引用 |
OpPushNumber | 压入数字字面量 |
OpPushString | 压入字符串字面量 |
OpPushBool | 压入布尔字面量 |
OpPop | 弹出栈顶元素 |
OpCondition | 条件判断 |
OpCompareOpcode | 比较操作码 |
OpCompareString | 比较字符串 |
OpEq | 等于 |
OpNotEq | 不等于 |
OpGt | 大于 |
OpGtEq | 大于等于 |
OpLt | 小于 |
OpLtEq | 小于等于 |
OpLogicAnd | 逻辑与 |
OpLogicOr | 逻辑或 |
OpLogicBang | 逻辑非 |
OpReMatch | 正则匹配 |
OpGlobMatch | 模糊匹配 |
OpNot | 取反 |
OpAlert | 输出变量 |
OpCheckParams | 检查参数 |
OpAddDescription | 添加描述 |
OpCreateIter | 创建迭代器 |
OpIterNext | 迭代器获取下一个元素 |
OpIterEnd | 迭代器结束 |
OpCheckStackTop | 检查栈顶元素 |
OpFilterExprEnter | 进入过滤表达式 |
OpFilterExprExit | 退出过滤表达式 |
OpMergeRef | 合并引用 |
OpRemoveRef | 移除引用 |
OpIntersectionRef | 求交集引用 |
OpNativeCall | 原生调用 |
OpFileFilterReg | 文件正则过滤 |
OpFileFilterXpath | 文件 XPath 过滤 |
OpFileFilterJsonPath | 文件 JsonPath 过滤 |
package com.test.example;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.*;
public class CommandInjectionServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String userInput = request.getParameter("command");
String command = "cmd.exe /c " + userInput; // 直接使用用户输入
Process process = Runtime.getRuntime().exec(userInput);
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
PrintWriter out = response.getWriter();
while ((line = reader.readLine()) != null) {
out.println(line);
}
}
}
Runtime.getRuntime().exec(,* as $sink);
sf 0| - enter -
sf 1| check top
sf 1| |-- >> stack top is nil (push input)
sf 2|
sf 3| push$exact Runtime isMember[name]
sf 3| |-- >> pop match exactly: Runtime
sf 3| |-- result next: Values: 1
sf 3| | 0 (t24): Undefined-Runtime
sf 3| |-- << push next
sf 4| push$exact getRuntime isMember[key]
sf 4| |-- >> pop match exactly: getRuntime
sf 4| |-- result next: Values: 1
sf 4| | 0 (t26): Undefined-Runtime.getRuntime
sf 4| |-- << push next
sf 5| getCall
sf 5| |-- >> pop
sf 5| |-- << push len: 1
sf 6| push$exact exec isMember[key]
sf 6| |-- >> pop match exactly: exec
sf 6| |-- result next: Values: 1
sf 6| | 0 (t29): Undefined-.exec
sf 6| |-- << push next
sf 7| - enter -
sf 8| getCallArgs 1
sf 8| |-- -- peek
sf 8| |-- - get argument: Values: 1
sf 8| | 0 (t21): Undefined-request.getParameter(Parameter-request,"command")
sf 8| |-- << push arg len: 1
sf 8| |-- << stack grow
sf 9| check top
sf 10|
sf 11| /
sf 12| update$ref sink
sf 12| |-- >> pop
sf 12| |-- -> save $sink
sf 13| - exit -
sf 14| getCall
sf 14| |-- >> pop
sf 14| |-- << push len: 1
sf 15| /
sf 16| pop
sf 16| |-- >> pop 1
sf 16| |-- save-to $_
sf 17|
getCallArgs
找到调用的函数参数。SELECT count(*) FROM "ir_indices" WHERE "ir_indices"."deleted_at" IS NULL AND ((( program_name = 'com.example' ) OR ( program_name = 'test' )) AND (variable_name = 'Runtime' OR class_name = 'Runtime' OR field_name = 'Runtime'))
SELECT * FROM "ir_indices" WHERE "ir_indices"."deleted_at" IS NULL AND ((( program_name = 'com.example' ) OR ( program_name = 'test' )) AND (variable_name = 'Runtime' OR class_name = 'Runtime' OR field_name = 'Runtime')) LIMIT 100 OFFSET 0
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '24')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_sources" WHERE (source_code_hash = '0ef46d9369a641a2a274c753208a7f982b7b376bd1d748b2ec7fa7e6f7e47f7e') LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '11')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_sources" WHERE (source_code_hash = '0ef46d9369a641a2a274c753208a7f982b7b376bd1d748b2ec7fa7e6f7e47f7e') LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '9')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '13')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_types" WHERE "ir_types"."deleted_at" IS NULL AND (("ir_types"."id" = 19)) ORDER BY "ir_types"."id" ASC LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '15')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '16')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '17')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '12')) ORDER BY "ir_codes"."id" ASC LIMIT 1
...
...
SELECT * FROM "ir_sources" WHERE (source_code_hash = '0ef46d9369a641a2a274c753208a7f982b7b376bd1d748b2ec7fa7e6f7e47f7e') LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '11')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '44')) ORDER BY "ir_codes"."id" ASC LIMIT 1
...
...
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '25')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '26')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_types" WHERE "ir_types"."deleted_at" IS NULL AND (("ir_types"."id" = 15)) ORDER BY "ir_types"."id" ASC LIMIT 1
SELECT * FROM "ir_sources" WHERE (source_code_hash = '0ef46d9369a641a2a274c753208a7f982b7b376bd1d748b2ec7fa7e6f7e47f7e') LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '11')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '12')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_types" WHERE "ir_types"."deleted_at" IS NULL AND (("ir_types"."id" = 2)) ORDER BY "ir_types"."id" ASC LIMIT 1
SELECT * FROM "ir_sources" WHERE (source_code_hash = '0ef46d9369a641a2a274c753208a7f982b7b376bd1d748b2ec7fa7e6f7e47f7e') LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '11')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '12')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_types" WHERE "ir_types"."deleted_at" IS NULL AND (("ir_types"."id" = 21)) ORDER BY "ir_types"."id" ASC LIMIT 1
SELECT * FROM "ir_sources" WHERE (source_code_hash = '0ef46d9369a641a2a274c753208a7f982b7b376bd1d748b2ec7fa7e6f7e47f7e') LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '11')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '12')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '30')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '28')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '29')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_types" WHERE "ir_types"."deleted_at" IS NULL AND (("ir_types"."id" = 12)) ORDER BY "ir_types"."id" ASC LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '26')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '24')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_sources" WHERE (source_code_hash = '0ef46d9369a641a2a274c753208a7f982b7b376bd1d748b2ec7fa7e6f7e47f7e') LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '11')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '12')) ORDER BY "ir_codes"."id" ASC LIMIT 1
SELECT * FROM "ir_types" WHERE "ir_types"."deleted_at" IS NULL AND (("ir_types"."id" = 2)) ORDER BY "ir_types"."id" ASC LIMIT 1
SELECT * FROM "ir_sources" WHERE (source_code_hash = '0ef46d9369a641a2a274c753208a7f982b7b376bd1d748b2ec7fa7e6f7e47f7e') LIMIT 1
SELECT * FROM "ir_codes" WHERE "ir_codes"."deleted_at" IS NULL AND ((id = '11')) ORDER BY "ir_codes"."id" ASC LIMIT 1
...
...
指路:https://github.com/yaklang/syntaxflow
第一象限 - 详细安全评估 亟需修复
servlet-command-inject-path: 这个规则检测 Servlet 代码中可能存在的命令注入漏洞,严重程度高,需要重点关注。
xxe:saxparser-factory: 这个规则检测 XML 外部实体注入漏洞,也属于高危漏洞,需要尽快修复。
第二象限 - 容易犯错的代码 重点关注
filedownload-attachment-filename: 这个规则检测文件下载时文件名的安全性,如果设置不当可能会导致一些安全问题。
sqlstring-append-query: 这个规则检测 SQL 语句拼接,如果拼接不当可能会导致 SQL 注入漏洞。
第三象限 - 选择性忽略
xss-filter-bypassing-prompt: 这个规则检测 XSS 过滤器的绕过,如果绕过不当可能会导致 XSS 漏洞,但危害较小。
第四象限 - 按需修复
websecurity-csrf-disabled-simple: 这个规则检测 CSRF 防护是否被禁用,如果禁用不当可能会导致 CSRF 漏洞,但危害较小。
规则名 | 规则说明 | 级别 | 类型 | 额外信息 |
java-servlet-command-inject-path.sf | Java Servlet 路径命令注入(仅可达路径) | 高 | 漏洞 | CWE-77 命令注入 |
java-servlet-n-spring-directly-command-injection.sf | Java Servlet 和 Spring 直接命令注入 | 高 | 漏洞 | CWE-77 命令注入 |
java-filedownload-attachment-filename.sf | 文件下载附件文件名 | 信息 | 漏洞 | |
java-command-exec-misc.sf | 命令执行杂项 | 信息 | 审计 | lib |
java-controller-params.sf | 控制器参数 | 信息 | 审计 | lib |
java-process-builder.sf | 进程构建器 | 信息 | 审计 | lib |
java-servlet-params.sf | Servlet 参数 | 信息 | 审计 | lib |
java-write-filename-params.sf | 写入文件名参数 | 信息 | 审计 | lib |
java-el-expr-factory-use.sf | 表达式语言工厂使用 | 信息 | 审计 | |
java-reflection-for-class-unsafe.sf | 不安全的类反射 | 信息 | 审计 | |
java-spel-parser-injection.sf | Spring 表达式语言解析器注入 | 信息 | 审计 | |
java-script-manager-eval.sf | 脚本管理器评估 | 信息 | 审计 | lib |
java-springframework-config-disable-csrf.sf | Spring 框架配置禁用 CSRF | 信息 | 漏洞 | |
java-xss-filter-bypassing-prompt.sf | XSS 过滤绕过提示 | 信息 | 审计 | |
java-jdbc-prepared-execute.sf | JDBC 预处理执行 | 信息 | 审计 | lib |
java-jdbc-raw-execute.sf | JDBC 原始执行 | 信息 | 审计 | lib |
java-multipart-file-transfer-to.sf | 多部分文件传输 | 信息 | 审计 | |
java-set-header-filedownload.sf | 设置文件下载头 | 信息 | 审计 | |
java-spring-resource-handler-location.sf | Spring 资源处理器位置 | 信息 | 审计 | |
java-springboot-websecurity-click-hijack-checking.sf | Spring Boot Web 安全点击劫持检查 | 信息 | 漏洞 | |
java-springfox-awared.sf | Springfox 使用 | 信息 | 审计 | |
java-sqlstring-append-query.sf | SQL 字符串追加查询 | 信息 | 审计 | |
java-websecurity-csrf-disabled-simple.sf | 简单禁用 CSRF 的 Web 安全 | 信息 | 漏洞 | |
java-saxbuilder-unsafe.sf | 不安全的 SAX 构建器 | 中 | 漏洞 | CWE-611 XXE |
java-saxparser-factory-unsafe.sf | 不安全的 SAX 解析器工厂 | 中 | 漏洞 | CWE-611 XXE |
java-saxreader-unsafe.sf | 不安全的 SAX 读取器 | 中 | 漏洞 | CWE-611 XXE |
java-springboot-filedownload.sf | Spring Boot 文件下载 | 中 | 漏洞 | |
java-springboot-multipart-files-write.sf | Spring Boot 多部分文件写入 | 中 | 审计 | |
java-xmlreader-factory-unsafe.sf | 不安全的 XML 读取器工厂 | 中 | 漏洞 | CWE-611 XXE |
java-xstream-unsafe.sf | 不安全的 XStream 使用 | 中 | 漏洞 | CWE-611 XXE |
java-runtime-exec.sf | 运行时执行 | 中 | 审计 | lib |
java-sax-transformer-factory-unsafe.sf | 不安全的 SAX 转换器工厂 | 中 | 漏洞 | CWE-611 XXE |
java-transformer-factory-unsafe.sf | 不安全的转换器工厂 | 中 | 漏洞 | CWE-611 XXE |
java-spring-el-use.sf | Spring 表达式语言使用 | 中 | 审计 | |
java-freemarker-model-put-params-sink.sf | Freemarker 模型参数传递 | 中 | 审计 | |
java-mybatis-to-springframework-param.sf | MyBatis 到 Spring 框架参数 | 中 | 漏洞 | CWE-89 SQL 注入 |
YAK官方资源
Yak 语言官方教程:
https://yaklang.com/docs/intro/
Yakit 视频教程:
https://space.bilibili.com/437503777
Github下载地址:
https://github.com/yaklang/yakit
https://github.com/yaklang/yaklang
Yakit官网下载地址:
https://yaklang.com/
Yakit安装文档:
https://yaklang.com/products/download_and_install
Yakit使用文档:
https://yaklang.com/products/intro/
常见问题速查:
https://yaklang.com/products/FAQ
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...