代码审计篇(从源码定位漏洞)
代码审计篇(从源码定位漏洞)
代码审计是SQL注入漏洞挖掘的核心进阶能力——相较于黑盒测试(盲猜漏洞),代码审计通过直接分析网站源码,精准定位漏洞产生的代码位置、判断漏洞类型及利用难度,是高权限利用、漏洞闭环修复的前置基础。本篇章严格围绕“源码定位漏洞”核心,拆解不同语言的漏洞特征、危险函数定位技巧及标准化审计流程,结合实战代码案例,帮助掌握从“看源码”到“找漏洞”的完整思路,实现高效、精准挖掘SQL注入漏洞。
1. 不同语言漏洞代码特征(精准识别,按语言归类)
SQL注入漏洞的本质是“用户输入未经过滤,直接拼接进SQL语句执行”,不同编程语言(PHP、Java、Python、ASP/ASP.NET)的数据库操作语法不同,漏洞代码的表现形式也存在差异,但核心特征一致。以下按实战高频语言分类,拆解漏洞代码特征、给出正反例对比,可直接套用在源码审计中。
1.1 PHP(最常用,漏洞场景最多)
PHP网站因开发门槛低、部署广泛,是SQL注入漏洞的高发场景,核心漏洞特征集中在“原生MySQL操作函数+未过滤的用户输入拼接”,常见漏洞代码模式:
核心漏洞特征:使用
mysql_query()等原生查询函数,直接拼接$_GET/$_POST/$_COOKIE等用户输入参数,未使用预处理(PDO、MySQLi预处理),无任何过滤或过滤不严格。漏洞代码案例(实战常见): `<?php
// 接收用户输入(无过滤)
$id = $_GET['id'];
// 直接拼接SQL语句(漏洞核心)
$sql = "select * from user where id = '$id'";
// 执行SQL查询(原生函数,无预处理)
$result = mysql_query($sql);
?>解析:用户输入的$id直接拼接进SQL语句,若传入id=1' and 1=2--+,将构造恶意SQL,触发注入漏洞;mysql_query()`函数已被废弃,且不支持预处理,是漏洞高发的核心原因。
- 安全代码案例(对比参考): `<?php
// 接收用户输入 $id = $_GET['id']; // 使用PDO预处理(安全写法) $pdo = new PDO("mysql:host=localhost;dbname=test", "root", "123456"); $sql = "select * from user where id = ?"; // 占位符,不直接拼接 $stmt = $pdo->prepare($sql); $stmt->execute([$id]); // 参数单独传入,自动过滤 $result = $stmt->fetchAll(); ?>`
- 补充漏洞特征:使用
mysqli_query()但未开启预处理、自定义过滤函数存在绕过(如仅过滤单引号,未过滤宽字节%df')、使用eval()拼接SQL语句(高危,可直接执行恶意代码)。
1.2 Java(企业级项目,漏洞更隐蔽)
Java企业级项目(如SSM、SpringBoot框架)通常使用JDBC操作数据库,漏洞核心特征是“JDBC原生拼接SQL,未使用PreparedStatement预处理”,漏洞代码相对隐蔽,需重点关注DAO层(数据访问层)。
核心漏洞特征:使用JDBC原生
Statement对象执行SQL、直接拼接用户输入参数(如request.getParameter()获取的参数)、未使用PreparedStatement(预处理对象),框架自带的ORM工具(如MyBatis)使用不当也会产生漏洞。漏洞代码案例(实战常见): `// 1. JDBC原生拼接(漏洞)
String id = request.getParameter("id"); // 接收用户输入(无过滤) String sql = "select * from user where id = '" + id + "'"; // 直接拼接 Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery(sql); // 执行SQL,触发注入
// 2. MyBatis框架使用不当(漏洞)
// Mapper.xml中直接拼接参数(未使用#{}占位符,使用${}拼接)
解析:JDBC中Statement对象不支持预处理,拼接用户输入会直接触发注入;MyBatis中${}是字符串拼接,#{}是预处理占位符,误用${}`会产生漏洞(尤其是排序、分页场景)。
- 安全代码案例(对比参考): `// 1. JDBC预处理(安全)
String id = request.getParameter("id"); String sql = "select * from user where id = ?"; // 占位符 PreparedStatement pstmt = connection.prepareStatement(sql); pstmt.setString(1, id); // 参数单独设置,自动过滤 ResultSet rs = pstmt.executeQuery();
// 2. MyBatis安全写法 `
1.3 Python(框架为主,漏洞集中在拼接逻辑)
Python网站常用框架(Django、Flask)自带ORM工具,漏洞核心特征是“放弃框架ORM,使用原生SQL拼接,未使用参数化查询”,漏洞场景主要集中在自定义SQL查询、框架ORM使用不当。
核心漏洞特征:使用
sqlite3、pymysql等库的原生SQL拼接、直接拼接request.args/request.form获取的用户输入、未使用框架或库提供的参数化查询方法。漏洞代码案例(实战常见): `# 1. Flask框架 + pymysql(漏洞)
from flask import Flask, request import pymysql
app = Flask(name) db = pymysql.connect(host='localhost', user='root', password='123456', db='test') cursor = db.cursor()
@app.route('/user') def get_user(): id = request.args.get('id') # 接收用户输入 sql = "select * from user where id = '" + id + "'" # 直接拼接(漏洞) cursor.execute(sql) return cursor.fetchone()
2. Django框架使用原生SQL(漏洞)
from django.db import connection
def get_user(id): sql = f"select * from user where id = '{id}'" # f-string拼接(漏洞) cursor = connection.cursor() cursor.execute(sql) return cursor.fetchone()`
- 安全代码案例(对比参考): `# 1. Flask + pymysql 参数化查询(安全)
sql = "select * from user where id = %s" # 占位符(%s,不拼接) cursor.execute(sql, (id,)) # 参数单独传入,自动过滤
2. Django ORM(安全,无需手动写SQL)
from app.models import User def get_user(id): return User.objects.get(id=id) # ORM自动预处理,避免注入`
1.4 ASP/ASP.NET(传统项目,漏洞特征明确)
ASP(传统动态网页)、ASP.NET(.NET框架)项目漏洞核心特征是“SQL语句直接拼接用户输入参数”,ASP常用ADO对象,ASP.NET常用SqlCommand未使用参数化查询,漏洞场景集中在登录、查询功能。
核心漏洞特征:
ASP:使用
ADO对象(CreateObject("ADODB.Connection")),拼接Request.QueryString/Request.Form获取的参数;ASP.NET:使用
SqlCommand对象,直接拼接SQL语句,未使用Parameters参数化方法。漏洞代码案例(实战常见): `// 1. ASP 漏洞代码
<% Dim conn, sql, id id = Request.QueryString("id") ' 接收用户输入 Set conn = Server.CreateObject("ADODB.Connection") conn.Open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb" sql = "select * from user where id = '" & id & "'" ' 直接拼接(漏洞) Set rs = conn.Execute(sql) %>
// 2. ASP.NET 漏洞代码(C#) string id = Request.QueryString["id"]; string sql = "select * from user where id = '" + id + "'"; // 拼接漏洞 SqlCommand cmd = new SqlCommand(sql, conn); SqlDataReader dr = cmd.ExecuteReader();`
- 安全代码案例(对比参考): `// 1. ASP 安全写法(参数化)
sql = "select * from user where id = ?" Set cmd = Server.CreateObject("ADODB.Command") cmd.CommandText = sql cmd.Parameters.Append cmd.CreateParameter("id", adInteger, adParamInput, , id)
// 2. ASP.NET 安全写法(C#) string sql = "select * from user where id = @id"; // 占位符 SqlCommand cmd = new SqlCommand(sql, conn); cmd.Parameters.AddWithValue("@id", id); // 参数化,自动过滤 SqlDataReader dr = cmd.ExecuteReader();`
2. 危险函数 / 关键字定位(快速找漏洞,高效审计)
代码审计的核心是“快速定位危险代码”,无需逐行阅读全部源码,可通过“危险函数+关键字”精准定位SQL注入漏洞的可疑位置,再进一步分析过滤逻辑和拼接方式,判断是否可利用。以下按“定位维度”拆解,给出实战定位技巧。
2.1 核心定位维度1:直接拼接SQL的函数(必查)
不同语言中,“执行SQL语句”的函数的是漏洞定位的核心,只要这类函数的参数中包含“用户输入拼接”,大概率存在注入漏洞,按语言分类整理如下(实战可直接搜索这些函数):
| 编程语言 | 危险函数(直接执行SQL,需重点排查) | 排查要点 |
|---|---|---|
| PHP | mysql_query()、mysqli_query()(未预处理)、pdo->query() | 查看函数参数中的SQL语句,是否包含$_GET/$_POST等用户输入拼接 |
| Java | Statement.executeQuery()、Statement.execute()、MyBatis ${}拼接 | 排查DAO层,查看SQL语句是否拼接request参数、是否误用${} |
| Python | cursor.execute()(参数为拼接SQL)、connection.execute() | 查看execute()的第一个参数,是否是f-string拼接、字符串拼接 |
| ASP/ASP.NET | conn.Execute()(ASP)、SqlCommand.ExecuteReader()(未参数化) | 查看SQL语句是否使用&(ASP)、+(ASP.NET)拼接用户输入 |
2.2 核心定位维度2:无过滤的参数接收点(源头排查)
SQL注入的源头是“未过滤的用户输入”,可通过搜索“参数接收关键字”,定位用户输入的入口,再追踪参数传递路径,判断是否被拼接进SQL语句,常见参数接收关键字如下:
PHP:$_GET、$_POST、$_COOKIE、$_REQUEST、$_SERVER['QUERY_STRING'];
Java:request.getParameter()、request.getHeader()、request.getParameterValues();
Python(Flask):request.args、request.form、request.cookies、request.data;
Python(Django):request.GET、request.POST、request.COOKIES;
ASP/ASP.NET:Request.QueryString、Request.Form、Request.Cookies、Request.ServerVariables。
实战技巧:搜索到参数接收点后,按住Ctrl(或Command)点击参数变量,追踪其传递路径,看是否最终被拼接进SQL语句、是否经过过滤。
2.3 核心定位维度3:过滤函数弱校验(可绕过,重点关注)
部分项目会添加“过滤函数”,但过滤逻辑存在缺陷,可被绕过,这类代码也是漏洞重点,常见弱过滤场景及特征如下:
场景1:仅使用str_replace替换(单一过滤,可绕过)
<?php // 弱过滤函数(仅替换单引号,可绕过) function filter($str){ return str_replace("'", "", $str); // 仅删除单引号 } $id = filter($_GET['id']); $sql = "select * from user where id = '$id'"; // 拼接,仍有漏洞 ?>绕过方法:使用宽字节注入(传入%df'),%df被MySQL(GBK编码)解析为汉字,与'拼接为縗',str_replace无法删除,实现闭合。场景2:单层过滤,未递归过滤(可绕过)
// 弱过滤(仅过滤一次,可绕过) function filter($str){ $str = str_replace("union", "", $str); return $str; } // 绕过:传入ununionion,过滤后变为union,触发漏洞 $id = filter($_GET['id']); // id=ununionion select 1,2,3--+ $sql = "select * from user where id = '$id'"; ?>**场景3:过滤不全面(遗漏关键字/符号)**如仅过滤
select、union,未过滤sel/**/ect、UNioN(大小写混淆);仅过滤单引号,未过滤双引号、括号;仅过滤空格,未过滤%0a、/**/(空格替换),这类过滤可直接通过前文WAF绕过手法突破。场景4:自定义过滤函数存在逻辑漏洞如过滤后未重新赋值(
filter($id);,未写$id = filter($id););过滤函数仅在部分参数接收点使用,部分参数未过滤(如登录框过滤,搜索框未过滤),这类漏洞需重点排查“参数接收-过滤-拼接”的完整链路。
2.4 辅助定位关键字(快速缩小范围)
除了危险函数和参数接收点,可搜索以下关键字,快速定位SQL拼接、漏洞可疑位置,提升审计效率:
SQL关键字:select、union、from、where、insert、update、delete、drop;
拼接符号:+(Java/ASP.NET)、&(ASP)、.(PHP)、f-string(Python,f"");
数据库相关:database()、version()、load_file()、into outfile;
注释符号:--、#、/**/、/!/。
3. 审计流程(标准化操作,避免遗漏漏洞)
代码审计需遵循“标准化流程”,从“入口”到“漏洞验证”,逐步推进,避免盲目阅读源码、遗漏漏洞。核心流程分为5步,实战中可直接套用,覆盖所有SQL注入漏洞场景。
3.1 第一步:确定入口参数(漏洞源头)
核心:找到网站所有“用户可控制的输入参数”,即前文提到的“参数接收点”,明确每个参数的传递路径和使用场景,优先排查高频漏洞场景的参数:
高频场景参数:登录框(username、password)、查询功能(id、keyword、page)、详情页(id、uid)、后台操作(add、edit、delete对应的参数);
排查方法:搜索参数接收关键字(如$_GET、request.getParameter),整理所有用户可控参数,标注参数对应的功能模块(如id对应用户详情页)。
3.2 第二步:追踪参数传递(链路排查)
核心:针对第一步整理的参数,追踪其“完整传递链路”,明确参数从“接收”到“拼接进SQL”的所有环节,重点关注是否经过过滤、转换,避免遗漏中间处理逻辑。
实战技巧:
以PHP为例,接收参数
$id = $_GET['id'],按住Ctrl点击$id,查看其被赋值、传递的所有位置;若参数经过多次赋值(如
$uid = $id; $sql = "select * from user where uid = '$uid'"),需追踪至最终拼接SQL的位置;重点关注“跨文件传递”(如参数从index.php传递至model/user.php,再拼接SQL),避免遗漏文件间的处理逻辑。
3.3 第三步:检查过滤逻辑(关键判断)
核心:判断参数传递过程中,是否经过过滤、过滤函数是否存在缺陷(弱过滤),这是判断漏洞是否可利用的关键,分3种情况判断:
情况1:未经过任何过滤(高危)。参数直接从接收点拼接进SQL,无需绕过,直接可构造Payload利用;
情况2:经过过滤,但过滤存在缺陷(可利用)。如前文提到的str_replace单一过滤、单层过滤、过滤不全面,可通过绕过手法突破;
情况3:经过严格过滤(无漏洞)。如使用参数化查询、预处理,或过滤函数递归过滤所有敏感关键字/符号,无拼接逻辑,不存在注入漏洞。
实战技巧:找到过滤函数后,可手动测试过滤逻辑(如传入单引号、union关键字,看过滤后是否仍存在敏感内容),判断是否可绕过。
3.4 第四步:查看SQL拼接点(漏洞定位)
核心:找到参数最终“拼接进SQL语句”的位置,明确拼接方式(如单引号闭合、双引号闭合、无引号闭合),为后续构造Payload铺垫,重点关注3点:
拼接格式:参数是否被单引号/双引号包裹(如
id = '$id'、id = "$id"、id = $id),决定Payload的闭合方式;拼接位置:参数在SQL语句中的位置(如where条件后、union查询中、order by后),决定注入手法(如联合查询、盲注、报错注入);
执行函数:拼接后的SQL语句,使用哪个函数执行(如mysql_query()、Statement.executeQuery()),判断是否支持多语句执行、是否有回显。
示例:拼接点sql = "select * from user where id = '$id'",参数被单引号包裹,拼接在where条件后,使用mysql_query()执行,可构造Payloadid=1' and 1=2--+,触发布尔盲注。
3.5 第五步:构造Payload 验证(漏洞闭环)
核心:根据拼接方式、过滤逻辑,构造适配的Payload,验证漏洞是否可利用,形成“定位-判断-验证”的闭环,步骤如下:
- 构造测试Payload:根据拼接格式,构造简单测试Payload(如单引号闭合,传入
id=1'),查看是否触发SQL报错(判断漏洞存在性);
- 构造测试Payload:根据拼接格式,构造简单测试Payload(如单引号闭合,传入
- 构造利用Payload:若存在漏洞,结合过滤逻辑,构造可绕过过滤的Payload(如过滤union,传入
ununionion select 1,2,3--+);
- 构造利用Payload:若存在漏洞,结合过滤逻辑,构造可绕过过滤的Payload(如过滤union,传入
- 验证漏洞利用:执行Payload,查看是否能获取敏感数据(如库名、表名),或执行恶意操作(如文件读写),确认漏洞可利用;
- 记录漏洞信息:标注漏洞位置(文件路径、代码行号)、拼接方式、过滤逻辑、可利用Payload,为后续修复或高权限利用铺垫。
4. 审计实战注意事项(避坑关键)
优先排查高频场景:登录框、搜索框、详情页、后台CRUD操作(添加、编辑、删除),这些场景是SQL注入漏洞的高发区,可提升审计效率;
避免遗漏“隐蔽拼接”:如参数经过base64解码、加密后再拼接(需先解码/解密,再构造Payload);参数拼接在注释中、字符串中(如
sql = "select 'user_$id' from user");结合框架特性审计:不同框架的漏洞特征不同(如MyBatis的${}、Django的原生SQL拼接),需熟悉框架的数据库操作方式,避免遗漏框架相关漏洞;
区分“不可利用漏洞”:如过滤严格、使用参数化查询的代码,即使存在拼接逻辑,也无注入漏洞,无需浪费时间;
合法性提醒:代码审计仅用于合法的渗透测试场景,需提前获得目标源码及服务器授权,严禁未经授权审计他人源码、挖掘漏洞用于非法攻击,否则将承担相应法律责任。
(注:文档部分内容可能由 AI 生成)