最近研究了一下PHP的代码审计,为了更快速审计php代码,实现一个静态审计工具。
- 实现了对PHP的opcode解析
- 基于1实现了对函数以及参数的分析
- 基于1实现了对变量和函数参数是否静态的分析
- 可通过yaml动态定义规则
为了方便使用,实现了VSCode的扩展,截图如下
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php $x = 'phpinfo'; $a = $x();
include($x . date('a'));
include(CCCCCC . "asddasd.php"); include($_POST['pass']); @eval($_POST['pass']);
eval("asd$x");
system(strtolower("asdasd")); system(xxx()); `$a`;
|
代码的opcode解析
opt1 opt2 rt 分别是操作数1,操作数2,返回值的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| +--------+--------------------+--------+-------------+--------+---+------------+-----------+------------+ | lineno | opcode | op1 | op2 | return | ex| opt1 | opt2 | rt | +--------+--------------------+--------+-------------+--------+---+------------+-----------+------------+ | 2 | ASSIGN | $x | phpinfo | | 0 | IS_CV | IS_CONST | IS_UNUSED | | 3 | INIT_DYNAMIC_CALL | | $x | | 0 | IS_UNUSED | IS_CV | IS_UNUSED | | 3 | DO_FCALL | | | V3 | 0 | IS_UNUSED | IS_UNUSED | IS_VAR | | 3 | ASSIGN | $a | V3 | | 0 | IS_CV | IS_VAR | IS_UNUSED | | 5 | INIT_FCALL | | date | | 1 | IS_UNUSED | IS_CONST | IS_UNUSED | | 5 | SEND_VAL | a | | | 0 | IS_CONST | IS_UNUSED | IS_UNUSED | | 5 | DO_ICALL | | | V5 | 0 | IS_UNUSED | IS_UNUSED | IS_VAR | | 5 | CONCAT | $x | V5 | T6 | 0 | IS_CV | IS_VAR | IS_TMP_VAR | | 5 | INCLUDE_OR_EVAL | T6 | | | 2 | IS_TMP_VAR | IS_UNUSED | IS_UNUSED | | 7 | FETCH_CONSTANT | | CCCCCC | T8 | 8 | IS_UNUSED | IS_CONST | IS_TMP_VAR | | 7 | CONCAT | T8 | asddasd.php | T9 | 0 | IS_TMP_VAR | IS_CONST | IS_TMP_VAR | | 7 | INCLUDE_OR_EVAL | T9 | | | 2 | IS_TMP_VAR | IS_UNUSED | IS_UNUSED | | 8 | FETCH_R | _POST | | T11 | 2 | IS_CONST | IS_UNUSED | IS_TMP_VAR | | 8 | FETCH_DIM_R | T11 | pass | T12 | 0 | IS_TMP_VAR | IS_CONST | IS_TMP_VAR | | 8 | INCLUDE_OR_EVAL | T12 | | | 2 | IS_TMP_VAR | IS_UNUSED | IS_UNUSED | | 9 | BEGIN_SILENCE | | | T14 | 0 | IS_UNUSED | IS_UNUSED | IS_TMP_VAR | | 9 | FETCH_R | _POST | | T15 | 2 | IS_CONST | IS_UNUSED | IS_TMP_VAR | | 9 | FETCH_DIM_R | T15 | pass | T16 | 0 | IS_TMP_VAR | IS_CONST | IS_TMP_VAR | | 9 | INCLUDE_OR_EVAL | T16 | | | 1 | IS_TMP_VAR | IS_UNUSED | IS_UNUSED | | 9 | END_SILENCE | T14 | | | 0 | IS_TMP_VAR | IS_UNUSED | IS_UNUSED | | 11 | NOP | | | | 0 | IS_UNUSED | IS_UNUSED | IS_UNUSED | | 11 | FAST_CONCAT | asd | $x | T18 | 0 | IS_CONST | IS_CV | IS_TMP_VAR | | 11 | INCLUDE_OR_EVAL | T18 | | | 1 | IS_TMP_VAR | IS_UNUSED | IS_UNUSED | | 13 | INIT_FCALL | | system | | 1 | IS_UNUSED | IS_CONST | IS_UNUSED | | 13 | INIT_FCALL | | strtolower | | 1 | IS_UNUSED | IS_CONST | IS_UNUSED | | 13 | SEND_VAL | asdasd | | | 0 | IS_CONST | IS_UNUSED | IS_UNUSED | | 13 | DO_ICALL | | | V20 | 0 | IS_UNUSED | IS_UNUSED | IS_VAR | | 13 | SEND_VAR | V20 | | | 0 | IS_VAR | IS_UNUSED | IS_UNUSED | | 13 | DO_ICALL | | | | 0 | IS_UNUSED | IS_UNUSED | IS_UNUSED | | 14 | INIT_FCALL | | system | | 1 | IS_UNUSED | IS_CONST | IS_UNUSED | | 14 | INIT_FCALL_BY_NAME | | xxx | | 0 | IS_UNUSED | IS_CONST | IS_UNUSED | | 14 | DO_FCALL_BY_NAME | | | V22 | 0 | IS_UNUSED | IS_UNUSED | IS_VAR | | 14 | SEND_VAR | V22 | | | 0 | IS_VAR | IS_UNUSED | IS_UNUSED | | 14 | DO_ICALL | | | | 0 | IS_UNUSED | IS_UNUSED | IS_UNUSED | | 15 | INIT_FCALL | | shell_exec | | 1 | IS_UNUSED | IS_CONST | IS_UNUSED | | 15 | CAST | $a | | T24 | 6 | IS_CV | IS_UNUSED | IS_TMP_VAR | | 15 | SEND_VAL | T24 | | | 0 | IS_TMP_VAR | IS_UNUSED | IS_UNUSED | | 15 | DO_ICALL | | | | 0 | IS_UNUSED | IS_UNUSED | IS_UNUSED | +--------+--------------------+--------+-------------+--------+---+------------+-----------+------------+
|
静态变量分析
根据规则,对每个变量在不同阶段其值是否静态做出分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| +------+-----+--------+--------+----------------+ | name | pos | lineno | static | reason | +------+-----+--------+--------+----------------+ | $x | 0 | 2 | True | ASSIGN | | V3 | 2 | 3 | False | init | | $a | 3 | 3 | False | ASSIGN | | V5 | 6 | 5 | True | date | | T6 | 7 | 5 | True | CONCAT | | T8 | 9 | 7 | True | FETCH_CONSTANT | | T9 | 10 | 7 | True | CONCAT | | T11 | 12 | 8 | False | init | | T12 | 13 | 8 | False | FETCH_DIM_R | | T14 | 15 | 9 | False | init | | T15 | 16 | 9 | False | init | | T16 | 17 | 9 | False | FETCH_DIM_R | | T18 | 21 | 11 | True | FAST_CONCAT | | V20 | 26 | 13 | True | strtolower | | V22 | 31 | 14 | False | init | | T24 | 35 | 15 | False | CAST | +------+-----+--------+--------+----------------+
|
函数分析
分析所有函数的名称、参数个数,参数变量,参数是否静态
1 2 3 4 5 6 7 8 9 10 11
| +------------+--------+--------+------------+----------+ | name | lineno | arglen | args | argtypes | +------------+--------+--------+------------+----------+ | $x | 3 | 0 | [] | [] | | date | 5 | 1 | ['a'] | [True] | | strtolower | 13 | 1 | ['asdasd'] | [True] | | system | 13 | 1 | ['V20'] | [True] | | xxx | 14 | 0 | [] | [] | | system | 14 | 1 | ['V22'] | [False] | | shell_exec | 15 | 1 | ['T24'] | [False] | +------------+--------+--------+------------+----------+
|
规则
opcodes 规则举例
定义opcode的危害
name
opcode的名称
- 使用PHP7.4.3 可参考 https://github.com/php/php-src/blob/PHP-7.4.3/Zend/zend_vm_opcodes.h#L79
level
危害等级:高、中、低
group
危害组名
condition
条件判断,为空时表示不需要判断。
- 语法为python语法,使用eval运行(感兴趣可以尝试getshell)
- 多个条件为且的关系
- 可以使用的变量
op1
操作数1的值或者变量
op2
操作数2的值或者变量
result
返回的值或者变量
op1_type
操作数1的类型
op2_type
操作数2的类型
result_type
返回的类型
extended_value
扩展值
lineno
行号
op1_static
操作数1是否静态
op2_static
操作数1是否静态
- 可以使用的常量
- 操作数或变量的类型
IS_UNUSED
,IS_CONST
,IS_TMP_VAR
,IS_VAR
,IS_CV
- 扩展值常量
EVAL
,INCLUDE
,INCLUDE_ONCE
,REQUIRE
,REQUIRE_ONCE
,FETCH_GLOBAL
,FETCH_LOCAL
,FETCH_GLOBAL_LOCK
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| opcodes: - name: INIT_DYNAMIC_CALL level: 高 group: 动态函数
- name: INCLUDE_OR_EVAL level: 高 group: 动态代码 condition: - 'extended_value == EVAL' - 'not op1_static'
- name: INCLUDE_OR_EVAL level: 高 group: 动态包含 condition: - 'not op1_static' - 'extended_value != EVAL'
|
functions 规则举例
定义函数的危害
- 除condition能使用的变量外其他与opcodes 规则相同
- condition能使用的变量
arglen
函数参数个数
args
数组,函数参数值或变量
argtypes
数组,函数参数是否静态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| functions: - name: system level: 高 group: 命令 condition: - 'not argtypes[0]'
- name: file_put_contents level: 高 group: 文件:写 condition: - 'not argtypes[0]' - 'not argtypes[1]'
- name: file_put_contents level: 低 group: 文件:写 condition: - 'argtypes[0]' - 'not argtypes[1]'
|
statics 规则举例
定义函数返回是否为静态值
- 与functions规则类似,但没有group和level
- 无条件或者条件满足时,表示函数返回值为静态
1 2 3 4 5 6 7 8 9
| statics: - name: date - name: md5 - name: strtolower condition: - 'argtypes[0]' - name: substr condition: - 'argtypes[0]'
|
使用
- 安装VSCode插件,点击下载
- 打开一个包含php文件的目录
- 按钮功能分别为是否仅显示当前文件、审计所有文件、审计当前文件、编辑自定义规则
- 配置服务端地址,默认为公用api地址:
http://phpaudit.guage.cool/api
,有私有化需求可以联系我