本篇为安全静态分析第二篇,简单介绍了AST是什么、AST语法、以及基于AST来做SQL注入检测的原理及代码实现
了解AST
AST 是一个树状结构,其中每个节点表示源代码中的一个语法结构,如表达式、语句、函数声明等
源代码首先经过词法分析器(Lexer)生成一系列令牌(tokens),然后经过语法分析器(Parser)构建出 AST。AST 表示了源代码的语法结构,但不包含冗余的细节,如空格、注释等
以简单的输出Hello world来举例看下AST解析出来之后的结果
Java print('Hello world')
|
https://astexplorer.net/
![安全静态分析(2)- 基于AST编写自动化漏洞检测 安全静态分析(2)- 基于AST编写自动化漏洞检测]()
https://esprima.org/demo/parse.html#
![安全静态分析(2)- 基于AST编写自动化漏洞检测 安全静态分析(2)- 基于AST编写自动化漏洞检测]()
作为对这块没有了解的同学,看到上边的AST语义树会一脸懵逼,那么先介绍下两个编程语言中最基本的概念
表达式(Expression|expr):
-
定义:表达式是由操作数(operands)和运算符(operators)组成的代码片段,它产生一个值。表达式可以包含变量、常量、函数调用等。
-
特点:表达式的主要目的是计算和生成一个值。它可以嵌套,由更简单的表达式构成。
-
例子:在 Python 中,2 + 3 就是一个表达式,它的值为 5。函数调用、赋值语句右侧也可以是表达式。
Python pythonCopy code x = 2 + 3 # 表达式 2 + 3 在赋值语句中 y = x * 4 # 表达式 x * 4 在赋值语句中
|
语句(Statement|stmt):
-
定义:语句是一条执行操作的独立单元,它表示一种动作或控制结构。语句执行后通常会产生副作用,如修改变量的值、控制程序的流程等。
-
特点:语句通常包含关键字(如 if、for、while、return)和表达式,它们是程序的基本构建块。
-
例子:在 Python 中,if 语句、for 循环语句、赋值语句都是语句。
Python pythonCopy code if x > 0: # if 语句是一个语句块 print("x is positive") # 这是一个表达式语句 for i inrange(3): # for 循环语句 print(i) # 这是一个表达式语句
|
AST语法
在 Python 中,ast 模块提供了用于生成和操作 AST 的工具。以下是一些 Python AST 的节点类型:
详细的语法请参考https://docs.python.org/zh-cn/3/library/ast.html
![安全静态分析(2)- 基于AST编写自动化漏洞检测 安全静态分析(2)- 基于AST编写自动化漏洞检测]()
使用python ast模块来实现一个简单的代码解析
Java import ast code = "2 + 3 * 4" parsed_ast = ast.parse(code) # 输出 AST print(ast.dump(parsed_ast))
|
输出结果为
Java Module(body=[Expr(value=BinOp(left=Constant(value=2, kind=None), op=Add(), right=BinOp(left=Constant(value=3, kind=None), op=Mult(), right=Constant(value=4, kind=None))))], type_ignores=[])
|
使用AST来检测SQL注入
检测的逻辑为
对此详细分析下
第一步 检测特征函数
要检测SQL注入,此处以常用的SQL代码语句为例
那么其在AST中解析为
Java Module(body=[Expr(value=Call(func=Attribute(value=Name(id='cursor', ctx=Load()), attr='execute', ctx=Load()), args=[], keywords=[]))], type_ignores=[])
|
那么根据这个特征结合AST解析出来的结果来写代码进行匹配
使用isinstance()函数判断对象是否为某一类,判断当前节点函数为Attribute类、且值为Name类型,对应的值id为cursor、attr为execute,那么就为检测到cursor.execute,其实就是把代码语句正向使用ast解析出来后,根据其ast结构写代码来匹配特征即可
Java isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name) and node.func.value.id == 'cursor' and node.func.attr == 'execute'
|
第二步 检测特征参数
检测特征函数,只是帮助我们快速定位SQL注入可能发生的点,但无法帮助我们检测是否存在风险
要想知道是否存在风险,要看具体的SQL语句编写方式,如果存在拼凑则一般就存在SQL注入
Java code_with_sql_injection = """ cursor.execute("SELECT * FROM Users WHERE username = '" + userInput + "';") """ code_without_sql_injection = """ cursor.execute("SELECT * FROM Users WHERE username = %s;", (userInput,)) """
|
通过我们对存在漏洞以及安全规范写法的差异来区别
1参数数量不同,存在漏洞的前者仅1个、后者大于1个
1参数表达式不同,存在漏洞的前者使用operator add来拼接字符串,后者没有
因此我们可以通过这种不同来检测
Java 第一种方案: if len(node.args)>1: return is_safe else: return is_vuln 第二种方式:基于拼凑字符串+ if '+' in node.args[0]: return is_vuln else return is_safe 第三种方式:基于ast oprator add if instance(node.args[0].op,ast.Add): return is_vuln else: return is_safe
|
第三步 实现检测SQL注入Demo代码
将前两步串联起来,再结合python ast的规范使用,就可以基本上得到ast检测sql注入的demo
Java #!/usr/bin/env python # -*- coding: UTF-8 -*- ''' AST检测 ''' import ast import astor class SQLInjectionAnalyzer(ast.NodeVisitor): def __init__(self): self.sql_injection_detected = False def visit_Call(self, node): # 检查是否调用了 SQL 查询函数,这里简单地以 execute 为例 if isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name) and node.func.value.id == 'cursor' and node.func.attr == 'execute': # 获取 SQL 查询字符串 sql_query = astor.to_source(node.args[0]) if node.args else None # 简单的正则表达式检测可能的 SQL 注入 # sql_injection_pattern = r'(bSELECTb.*bFROMb)|(bDELETEb.*bFROMb)|(bUPDATEb.*bSETb)|(bINSERT INTOb)' if sql_query and any(keyword in sql_query for keyword in ['SELECT', 'DELETE', 'UPDATE', 'INSERT INTO']) and '+' in sql_query: self.sql_injection_detected = True print('Potential SQL injection detected. Review the following code snippets:') print(sql_query) self.generic_visit(node) # 示例用法 code_with_sql_injection = """ cursor.execute("SELECT * FROM Users WHERE username = '" + userInput + "';") """ code_without_sql_injection = """ cursor.execute("SELECT * FROM Users WHERE username = %s;", (userInput,)) """ # 使用 ast 模块解析代码并进行分析 analyzer_with_injection = SQLInjectionAnalyzer() ast_with_injection = ast.parse(code_with_sql_injection) analyzer_with_injection.visit(ast_with_injection) if not analyzer_with_injection.sql_injection_detected: print('No SQL injection risk detected in the code.') analyzer_without_injection = SQLInjectionAnalyzer() ast_without_injection = ast.parse(code_without_sql_injection) analyzer_without_injection.visit(ast_without_injection) if not analyzer_without_injection.sql_injection_detected: print('No SQL injection risk detected in the code.')
|
结果为
![安全静态分析(2)- 基于AST编写自动化漏洞检测 安全静态分析(2)- 基于AST编写自动化漏洞检测]()
原文始发于微信公众号(暴暴的皮卡丘):安全静态分析(2)- 基于AST编写自动化漏洞检测
评论