代码审计篇(从源码定位漏洞)

约 21 分钟读完

代码审计篇(从源码定位漏洞)

代码审计是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使用不当。

  • 核心漏洞特征:使用sqlite3pymysql等库的原生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:过滤不全面(遗漏关键字/符号)**如仅过滤selectunion,未过滤sel/**/ectUNioN(大小写混淆);仅过滤单引号,未过滤双引号、括号;仅过滤空格,未过滤%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”的所有环节,重点关注是否经过过滤、转换,避免遗漏中间处理逻辑。

实战技巧:

  1. 以PHP为例,接收参数$id = $_GET['id'],按住Ctrl点击$id,查看其被赋值、传递的所有位置;

  2. 若参数经过多次赋值(如$uid = $id; $sql = "select * from user where uid = '$uid'"),需追踪至最终拼接SQL的位置;

  3. 重点关注“跨文件传递”(如参数从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,验证漏洞是否可利用,形成“定位-判断-验证”的闭环,步骤如下:

    1. 构造测试Payload:根据拼接格式,构造简单测试Payload(如单引号闭合,传入id=1'),查看是否触发SQL报错(判断漏洞存在性);
    1. 构造利用Payload:若存在漏洞,结合过滤逻辑,构造可绕过过滤的Payload(如过滤union,传入ununionion select 1,2,3--+);
    1. 验证漏洞利用:执行Payload,查看是否能获取敏感数据(如库名、表名),或执行恶意操作(如文件读写),确认漏洞可利用;
    1. 记录漏洞信息:标注漏洞位置(文件路径、代码行号)、拼接方式、过滤逻辑、可利用Payload,为后续修复或高权限利用铺垫。

4. 审计实战注意事项(避坑关键)

  • 优先排查高频场景:登录框、搜索框、详情页、后台CRUD操作(添加、编辑、删除),这些场景是SQL注入漏洞的高发区,可提升审计效率;

  • 避免遗漏“隐蔽拼接”:如参数经过base64解码、加密后再拼接(需先解码/解密,再构造Payload);参数拼接在注释中、字符串中(如sql = "select 'user_$id' from user");

  • 结合框架特性审计:不同框架的漏洞特征不同(如MyBatis的${}、Django的原生SQL拼接),需熟悉框架的数据库操作方式,避免遗漏框架相关漏洞;

  • 区分“不可利用漏洞”:如过滤严格、使用参数化查询的代码,即使存在拼接逻辑,也无注入漏洞,无需浪费时间;

  • 合法性提醒:代码审计仅用于合法的渗透测试场景,需提前获得目标源码及服务器授权,严禁未经授权审计他人源码、挖掘漏洞用于非法攻击,否则将承担相应法律责任。

    (注:文档部分内容可能由 AI 生成)

← 登录界面的20种渗透思路 —— 图文解析 主流注入利用手法(核心实战模块) →