发布于  更新于 

PHP代码审计工具

最近研究了一下PHP的代码审计,为了更快速审计php代码,实现一个静态审计工具。

  1. 实现了对PHP的opcode解析
  2. 基于1实现了对函数以及参数的分析
  3. 基于1实现了对变量和函数参数是否静态的分析
  4. 可通过yaml动态定义规则

为了方便使用,实现了VSCode的扩展,截图如下

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$x = 'phpinfo';
$a = $x();
// $x 是静态、date返回根据规则是静态,所以忽略
include($x . date('a'));
// CCCCCC 和 "asddasd.php" 都是静态,所以忽略
include(CCCCCC . "asddasd.php");
include($_POST['pass']);
@eval($_POST['pass']);
// "asd" 和 $x 为静态,所以忽略
eval("asd$x");
// strtolower 根据规则其的参数是静态,其返回为静态 所以忽略
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的名称
    1. 使用PHP7.4.3 可参考 https://github.com/php/php-src/blob/PHP-7.4.3/Zend/zend_vm_opcodes.h#L79
  • level 危害等级:高、中、低
  • group 危害组名
  • condition 条件判断,为空时表示不需要判断。
    1. 语法为python语法,使用eval运行(感兴趣可以尝试getshell)
    2. 多个条件为且的关系
    3. 可以使用的变量
      1. op1 操作数1的值或者变量
      2. op2 操作数2的值或者变量
      3. result 返回的值或者变量
      4. op1_type 操作数1的类型
      5. op2_type 操作数2的类型
      6. result_type 返回的类型
      7. extended_value 扩展值
      8. lineno 行号
      9. op1_static 操作数1是否静态
      10. op2_static 操作数1是否静态
    4. 可以使用的常量
      1. 操作数或变量的类型IS_UNUSED,IS_CONST,IS_TMP_VAR,IS_VAR,IS_CV
      2. 扩展值常量 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能使用的变量
    1. arglen 函数参数个数
    2. args 数组,函数参数值或变量
    3. 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 规则举例

定义函数返回是否为静态值

  1. 与functions规则类似,但没有group和level
  2. 无条件或者条件满足时,表示函数返回值为静态
1
2
3
4
5
6
7
8
9
statics:
- name: date
- name: md5
- name: strtolower
condition:
- 'argtypes[0]'
- name: substr
condition:
- 'argtypes[0]'

使用

  1. 安装VSCode插件,点击下载
  2. 打开一个包含php文件的目录
  3. 按钮功能分别为是否仅显示当前文件、审计所有文件、审计当前文件、编辑自定义规则
  4. 配置服务端地址,默认为公用api地址,有私有化需求可以联系我