摘要:每一个安全漏洞,在被利用之前,都曾是一行静静躺在代码库中的普通代码。代码审计,正是那门在漏洞“行凶”之前,就将其从源代码中“缉拿归案”的终极艺术。本文将深入代码审计的核心方法论,系统性地介绍如何通过“危险函数定位”找到潜在的“犯罪现场”,如何通过“数据流跟踪分析”绘制出攻击者的完整行动路线,以及如何审查“过滤函数”并发现其绕过方式。本文旨在为你构建一套从“怀疑”到“验证”的、可落地的代码审计思维框架。
关键词: 代码审计, 安全编码, 静态分析, SAST, 数据流分析, 危险函数, 漏洞挖掘
⚠️ 严正声明与道德准则:以审计之心,行守护之责
本文所有技术、方法和示例,均以防御和教育为唯一目的,旨在帮助开发者和安全工程师识别并修复自己代码中的安全漏洞。代码审计是一项授权行为。严禁将本文内容用于任何形式的非法活动。
引言:从“黑盒”到“白盒”的上帝视角
在之前的文章中,我们探讨的大多是“黑盒测试”——我们不看应用的内部代码,只通过外部的输入输出来猜测其是否存在漏洞。
代码审计,则是“白盒测试”。我们拥有了应用的完整源代码,这给了我们“上帝视角”。我们可以清晰地看到:
数据是如何流入的?
途经了哪些处理逻辑?
最终流向了何方?
这使得我们能够发现那些极其隐蔽的、黑盒测试难以触及的深层次漏洞。
第一章:审计的“思维模式”——从“正向”到“反向”
正向追踪 (Forward Tracking):
思路: 从“入口”开始,跟着用户的输入数据“走”。
流程: 找到一个接收用户输入的“源头”(Source),如PHP的
$_GET
,$_POST
,然后一步步地跟踪这个变量,看它流经了哪些函数,最终是否进入了一个危险的“终点”(Sink)。优点: 符合直觉。
缺点: 在大型项目中,一个输入变量可能经过几十个函数的传递,容易“跟丢”。
反向追踪 (Backward Tracking) —— 专业审计者的首选:
思路: 从“终点”开始,逆流而上,寻找它的“源头”。
流程: 首先,在代码库中全局搜索所有已知的“危险函数”(Sink),将它们标记为潜在的“犯罪现场”。然后,对每一个“现场”,逆向分析传入其中的变量,看其是否最终可以被用户的输入所控制。
优点:目标明确,效率极高。
第二章:“犯罪现场”定位——危险函数(Sink)的识别
这是反向追踪的第一步。你需要有一份针对你所审计语言的“危险函数清单”。
漏洞类型 | PHP | Java | Python |
命令执行 | system , exec , shell_exec , ` | Runtime.exec , ProcessBuilder | os.system , subprocess.run(shell=True) |
代码执行 | eval , assert , preg_replace /e | SpEL, OGNL, 不安全的反序列化 | eval , exec , pickle.loads |
SQL注入 | mysqli_query , mysql_query | Statement.executeQuery | cursor.execute (配合字符串格式化) |
文件包含 | include , require , file_get_contents | - | - |
文件上传 | move_uploaded_file | - | - |
SSRF | curl_exec , file_get_contents | URL.openConnection | requests.get , urllib.request.urlopen |
XSS | echo , print , printf | out.println | render_template (不自动转义时) |
实战操作: 使用你的IDE(如VS Code, PhpStorm, IntelliJ)的全局搜索功能(Global Search),将这些关键字作为你的第一批目标,创建一个“高风险代码清单”。
第三章:“嫌疑人”追踪——数据流分析
现在,我们对一个“犯罪现场”进行深入调查。
场景: 我们在审计一个PHP应用,通过全局搜索,发现了一处危险的“Sink”:
PHP// file: /user/profile.php ... $sql = "SELECT * FROM users WHERE id = $user_id"; $result = mysqli_query($conn, $sql); // <-- 我们的Sink! ...
反向追踪开始:
第一步:分析Sink的直接输入。
mysqli_query
的第二个参数是$sql
。我们需要知道$sql
从哪里来。
第二步:向上追溯变量
$sql
。就在上一行,我们看到
$sql
是由字符串和变量$user_id
拼接而成的。这是一个强烈的危险信号!
第三步:继续向上追溯变量
$user_id
。使用IDE的“查找所有引用”(Find Usages)功能,或继续向上读代码。我们可能发现:
... $user_id = $_GET['id']; // <-- 源头(Source)找到了! ... $sql = "SELECT * FROM users WHERE id = $user_id"; $result = mysqli_query($conn, $sql); ...
结论: 我们成功地构建了一条完整的数据流链:
$_GET['id']
(Source) ->$user_id
->$sql
->mysqli_query
(Sink)。在这条链路上,没有任何的过滤或处理。这是一个典型的数字型SQL注入漏洞。
第四章:“安保系统”审查——寻找过滤函数及其绕过
并非所有的数据流都是“裸奔”的。在数据从Source流向Sink的过程中,常会经过一个或多个“安保检查站”——过滤/净化函数(Sanitization Functions)。
场景: 我们在追踪另一个漏洞时,发现了这样的代码:
PHP// file: /search.php ... $keyword = $_GET['q']; $safe_keyword = sanitize_input($keyword); // <-- 发现一个过滤函数! echo "
您搜索了: " . $safe_keyword . ""; // <-- Sink (XSS) ...审查流程:
定位过滤函数: 我们找到了一个自定义的过滤函数
sanitize_input()
。深入其内部逻辑(最关键的一步):
使用IDE的“跳转到定义”(Go to Definition)功能,我们查看
sanitize_input()
的源码:
// file: /lib/security.php function sanitize_input($data) {// 开发者试图通过一个简单的黑名单来防御XSS$data = str_replace("<script>", "", $data);$data = str_replace("onerror", "", $data);return $data; }
寻找绕过方式:
分析: 这是一个典型的、极其脆弱的黑名单过滤。它只过滤了小写的
<script>
和onerror
。构造Bypass Payload:
大小写绕过:
<sCrIpT>alert(1)</ScRiPt>
利用其他标签/事件:
<img src=x ONERROR=alert(1)>
(大写的ONERROR)双写绕过:
<sc<script>ript>alert(1)</script>
(如果使用的是str_replace
而非str_ireplace
)
结论: 过滤函数存在,但其逻辑过于简单,可以被轻松绕过。存储型XSS漏洞依然存在。
审查过滤函数的“心法”:
黑名单 vs. 白名单: 任何基于黑名单的过滤,都应被高度怀疑。健壮的防御永远是白名单。
“一次性”过滤: 过滤函数是否只执行了一次?攻击者可能通过双重编码等方式绕过。
上下文错误: 过滤函数是否用错了地方?例如,用
htmlspecialchars()
(防御XSS)去尝试防御SQL注入,这是无效的。
结论:从“代码阅读者”到“逻辑审查官”
代码审计的本质,不是简单地寻找代码中的“错别字”,而是要成为一名逻辑的“审查官”。它要求我们:
建立“危险意识”: 熟知各类危险函数,能够快速定位潜在的风险点。
具备“追踪能力”: 能够像侦探一样,耐心、细致地追踪数据在复杂代码中的完整流转路径。
拥有“批判性思维”: 绝不轻信任何一个所谓的“安全函数”,而是要深入其内部,审视其逻辑是否严谨,是否存在被绕过的可能。
将这种“反向追踪、深入分析、批判性思考”的思维模式,融入到你的每一次代码审查中,你就能在漏洞被利用之前,将其“捉拿归案”,从而构建出真正坚不可摧的软件系统。