最近我一直想找个能本地跑、又快又稳的代码模型做安全预检最后重点测了 Qwen2.5-Coder-1.5B 在 SQL 注入检测上的效果。选择这个场景不是因为SQL注入多时髦而是因为它的判定标准非常明确代码里有没有直接拼接用户输入、能不能在数据库层被利用以及修复后是否真的堵住了入口。1.5B 这个参数量级原本我以为顶多生成点样板代码但实测下来它给了我三样东西一段能直接跑的检测脚本、一份符合工程惯例的修复建议、一套从应用层到监控层的加固清单。这篇博文我会把完整的测试过程、模型输出原文和人工复核后的修正版本都放出来适合正在做 AI 辅助编程选型的人也适合想用低成本方式给项目做安全预检的开发者。1. 为什么拿 SQL 注入来测 1.5B 小模型先说清楚思路1.1 测试目标小模型在安全任务上到底行不行先说我的判断标准。大模型做代码生成现在已经不稀奇了真正难的是在资源受限的环境里比如一台只有 8GB 内存的笔记本或者不能把业务代码传到公网服务器的场景下还能不能完成高质量的安全编码任务。Qwen2.5-Coder-1.5B 恰好卡在这个定位上它足够小量化之后连内存占用都能压到 2GB 左右又属于代码专用模型不是拿通用对话模型硬凑数。所以我的目标很直接不看它能不能写出惊艳的框架只看它能不能在 SQL 注入这种边界清楚、规则相对成熟的任务上给出可用的检测代码和修复方案。SQL 注入是个很好的试金石。它不像性能优化那样有模糊的权衡空间也不像架构设计那样依赖上下文。一段 Java 代码里用字符串拼接 SQL风险点就是明确的修复方式就是参数化查询或者输入校验加固方案就是层层设防。这类任务如果模型能做好说明它至少掌握了一套完整的安全知识结构而不是只背了几个代码片段。另一个私心是SQL 注入场景的验证成本极低。我可以随手写一个模拟订单查询接口再把模型的输出跑一遍看它能不能真正发现问题。这种“可验证性”很重要模型回答得漂亮和回答得正确是两回事我见过太多语言流畅但代码跑不通的生成结果了。1.2 测试方案一段危险代码让模型输出三段内容测试方案我设计得很简单准备一段故意留了注入风险的生产风格代码交给模型要求它输出三个内容——检测代码、修复建议、安全加固方案。为什么是这三个而不是别的因为它们刚好覆盖了安全编码的完整闭环先发现问题再动手修复最后思考怎么防止别的地方再出现同类问题。我准备的目标代码是一段 Java 的订单查询接口模拟的是很多老系统里真实存在的情况。伪代码如下public ListOrder queryOrders(String userId, String status) { String sql SELECT id, amount, status FROM orders WHERE user_id userId AND status status ; Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql); // 省略结果集处理 }这段代码两个参数都直接拼进了 SQL 字符串如果 userId 传进来的是1001 OR 11整个查询条件就会被改写直接越权读取全部订单。我把这段代码塞给模型不加任何预引导只告诉它用安全工程师的视角来审。这里有个测试原则我想强调不要喂模型一堆 hint然后让它顺着你的思路走。那样测出来的是提示词的效果不是模型本身的能力。我第一轮只给代码和一个任务描述模型能主动说出哪里有问题、怎么检测、怎么修才是真实的水平。1.3 环境准备本地 8GB 内存就能跑起来跑这个模型比我想象的省事。我用 ollama 直接拉取模型一条命令就搞定ollama run qwen2.5-coder:1.5b。它会自动拉模型文件并做量化默认的量化格式对内存很友好。我的测试机器是 16GB 内存的普通笔记本没有独立显卡跑起来虽然不能跟云端大模型比速度但生成一段几百字的代码也就几秒钟完全在可接受的范围内。如果你的机器内存只有 8GB也别太担心。1.5B 量化后参数量本身占用大约 1GB 左右加上推理过程中的临时开销实际占用通常在 2GB 到 4GB 之间普通电脑都能带得动。想用 Transformer 生态的话也可以用transformers库直接加载设置device_mapautoCPU 模式下也能跑。还有人喜欢用 llama.cpp 自己编译效果类似就是折腾一点。环境就绪后我做的第一件事不是直接上测试代码而是先跑了一段无关的代码生成请求看看模型的基本输出质量。确认它确实是个正经的代码模型不是那种胡言乱语的玩具模型之后才开始正式的注入测试。2. 核心环节让模型生成 SQL 注入检测代码2.1 提示词设计三个关键约束不能少很多人用代码模型喜欢丢一句话过去就等结果这样出来的东西往往不可用。我这轮测试的提示词刻意放进了三个约束每个约束都有明确的目的。第一是角色约束。我明确说“你是一名安全工程师”而不是笼统地说“请审一下这段代码”。角色设定会让模型自动调用安全领域知识输出风格也更贴近专业审计而不是中学老师批作业。第二是输出形态约束。我要求检测逻辑整理成一个独立的 Python 脚本并强调“脚本必须能直接运行”。这很重要因为模型有时候会给你一段伪代码看着对跑不起来。要求“直接运行”能逼它补全函数定义、导入语句和测试用例。第三是范围约束。我要求它输出的检测规则用于“测试环境的自检”不要写攻击利用步骤。这不是为了应付审核而是为了让模型输出更聚焦在防御侧。如果让模型自由发挥它会忍不住写一堆绕过技巧对实际防御帮助不大还容易引入风险。我实际使用的完整提示词大概这样你是一名安全工程师。下面这段 Java 代码存在 SQL 注入风险请完成以下三件事 1. 写一个 Python 脚本用于检测输入字符串中常见的 SQL 注入特征脚本要能直接运行并包含若干测试用例 2. 给出修复这段 Java 代码的具体建议 3. 从应用层到数据库层给出完整的安全加固方案。 注意只做防御性检测不要输出攻击利用方法不要输出完整的漏洞利用载荷。 目标代码 [贴入上面的 Java 片段]模型输出的第一段就是一个 Python 检测脚本以下是它生成的原始版本我原样保留import re def check_sql_injection(input_str): patterns [ r(|\)\s*(OR|AND)\s.*?.*?, r--, r;\s*(DROP|DELETE|UPDATE|INSERT|SELECT), rUNION\sSELECT, rsleep\s*\(, rinformation_schema, rextractvalue|updatexml|xpath, ] for pattern in patterns: if re.search(pattern, input_str, re.IGNORECASE): return True, pattern return False, None test_inputs [ 1001, 1001 OR 11, 1; DROP TABLE orders; --, 1 AND SLEEP(5)--, ] for t in test_inputs: flag, matched check_sql_injection(t) print(f{t!r}: {危险 if flag else 安全} (匹配: {matched}))运行这个脚本输出结果符合预期第一条“1001”判定安全后面三条全部命中危险模式。检测逻辑能覆盖联合查询、布尔注入、堆叠注入和时间盲注的基础形态对一个 1.5B 的小模型来说第一版输出到这个程度已经超出我的预期了。2.2 模型生成的检测代码与实测效果拿到脚本之后我又手动扩展了测试用例专门试探它的边界。我加了这几条输入extra_inputs [ 1001/**/OR/**/11, 1001%27%20OR%20%271%27%271, 1001 OR 11 #, 1001 AND 11, 正常用户001, ] for t in extra_inputs: flag, matched check_sql_injection(t) print(f{t!r}: {危险 if flag else 安全} (匹配: {matched}))结果表明模型给的检测规则对普通注入样本有效但存在明显的盲区/**/注释符替换空格的形式没有命中因为正则里没有覆盖注释符变体URL 编码形式%27直接绕过因为没有先解码再匹配#注释符虽然常见但正则列表里没有单独处理正常的“AND 11”反而被标记为危险因为AND和的组合触发了第一个正则这是误报。这个测试结果比模型本身更值得分析。它说明一个问题基于正则的检测方案天然有覆盖面和误报率的矛盾哪怕是成熟的商业产品也只能在两者之间找平衡点。1.5B 模型能给出一个结构完整、可运行、对基础攻击有效的初版框架但后续的规则迭代和边界处理仍然需要人来补。指望模型一次性给出生产级检测规则是不现实的指望它帮你把初版框架搭起来再节省大量从零编写的时间这个期望完全合理。2.3 检测逻辑的优缺点点评挨个说下模型输出代码的优点。第一它知道用re.IGNORECASE来覆盖大小写变体这对 SQL 关键字特别关键因为注入载荷经常混合大小写来绕过简单的字符串匹配。第二它选了information_schema、extractvalue、updatexml这些特征词说明模型对报错注入和元数据查询的常见手段有认知这些不是凭空编出来的。第三脚本整体是可运行的缩进正确、导入完整、测试入口齐全直接复制就能跑这一点很多大模型反而做得不好因为它们喜欢在代码里夹杂长篇解释。缺点同样明显。除了刚才验证出的注释符和编码盲区还有一个更隐蔽的问题它完全没有利用上下文信息。比如输入正常用户001里含有“001”这种数字串如果某个正则写得粗暴就很容易误杀。另外正则只能做单次输入检测没法覆盖“多次请求组合利用”的复杂场景比如先探测版本再联合查询的两阶段攻击。这些局限不是模型独有的基于规则的扫描器都这样但我要提醒读者AI 生成的检测脚本只能作为第一层过滤不能替代真正的数据库审计和运行时防护。我在实际使用时给模型输出补充了一个预处理步骤先对输入做 URL 解码和注释符归一化再喂给正则检测。这一步能解决大部分编码绕过问题代码量也不大。后面在问题排查部分我会给出具体的补充版本。3. 修复建议与安全加固方案的实操解读3.1 模型给出的修复建议逐条落地验证模型输出的修复建议一共有四条我逐条在模拟环境里做了验证。第一条是使用PreparedStatement参数化查询。这是最核心的修复手段也是所有安全编码规范的共识。它的原理简单说就是 SQL 语句结构在编译时已经确定参数只作为数据传入不会再被解释成 SQL 关键字。我照着建议改写了原 Java 代码查询功能保持正常注入载荷传入后变成了一串纯字符串数据库只当作普通字段值处理。这一条是必须做的不管其他措施做得多好只要这里漏了前面都是白搭。第二条是对输入做白名单校验。模型建议 userId 只允许字母、数字、下划线status 只允许固定枚举值。我在实际项目里一般会再加一步“长度限制”比如订单号不超过 32 位备注不超过 200 字符。长度限制看着简单却能有效阻断缓冲区溢出类攻击和超大 payload 的尝试属于低成本高收益的措施。第三条是数据库账号最小权限。模型建议业务账号只授予SELECT、INSERT、UPDATE等必要权限禁止DDL权限。这条我特别认可因为实际攻防中很多注入攻击根本目的不是为了读数据而是为了执行DROP或者写入恶意文件。数据库账号如果连删除表的权限都没有堆叠注入就算突破应用层也无处发力。第四条是关闭数据库错误信息回显。这里我要多说一句这条建议要分环境看待。开发环境开着详细错误信息是有助于排查问题的但生产环境必须关掉或者转接到内部日志系统。模型没有区分环境这是它的局限方案本身没错落地的时候得靠人做环境适配。3.2 加固方案从应用层到监控层的完整清单模型给的加固方案比我预期要系统不是零散的点子而是分层的。我整理成了一份可以直接对照落地的清单层级加固措施落地方式优先级应用层全面参数化查询替换所有字符串拼接 SQL 的代码最高应用层输入白名单与长度校验统一入口拦截器或参数校验框架高数据库层最小权限账号按业务划分数据库账号高数据库层关闭详细错误输出生产环境配置调整中接入层部署 WAF 规则过滤注入载荷网关层正则过滤或规则引擎中监控层SQL 异常与慢查询监控日志分析告警平台中这个清单本身没有硬伤但我必须补上模型没有主动提的一个关键点优先级排序。真正紧急的是应用层的参数化查询其他的都是防御纵深。如果项目里只有做一条的精力就做参数化查询如果还有余力再做输入校验和最小权限最后才考虑 WAF 和监控体系建设。模型把所有措施并列成一条清单没有帮你判断先做哪个这是它需要人工介入的地方。关于接入层的 WAF 规则我建议用现成规则引擎不建议自己从头写正则。我自己试过在模拟环境里配一条简单的过滤规则用的模式跟模型生成的检测脚本类似效果还行但生产环境必须做好误杀管理否则用户正常输入里带一个单引号全被拦下来业务投诉能把你淹了。模型生成的检测脚本更适合作为审计辅助不适合直接拿来当 WAF 引擎。3.3 用第二轮对话让模型补出修复代码第一轮输出里模型给出了修复建议的文字描述但没有直接给出改写后的 Java 代码。我追加了一轮对话让它把修复后的完整代码写出来。这轮我没有提供额外背景只是简单地说“请给出使用 PreparedStatement 修复后的完整 Java 方法”。模型生成的代码public ListOrder queryOrders(String userId, String status) { String sql SELECT id, amount, status FROM orders WHERE user_id ? AND status ?; try (PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setString(1, userId); pstmt.setString(2, status); try (ResultSet rs pstmt.executeQuery()) { ListOrder result new ArrayList(); while (rs.next()) { Order order new Order(); order.setId(rs.getLong(id)); order.setAmount(rs.getBigDecimal(amount)); order.setStatus(rs.getString(status)); result.add(order); } return result; } } catch (SQLException e) { log.error(查询订单失败, e); throw new RuntimeException(查询失败, e); } }这段代码质量超出我的预期几个细节尤其值得说。它用了try-with-resources来自动关闭连接和结果集避免了资源泄漏错误日志只记录到服务端没有把数据库异常信息直接暴露给前端参数绑定顺序和 SQL 中的占位符位置一一对应没有搞错。这些细节说明模型对 Java 工程实践有相当程度的理解。不过有一条边界它没有主动提如果排序字段是动态传入的比如前端传orderByamount、directiondesc这些表名和排序方向不能被参数化绑定必须单独用枚举白名单做映射。模型没想到这个场景是情有可原的因为很多刚接触安全开发的程序员也会在这里踩坑。我在这里提醒读者参数化查询不是万能的它只能解决值注入不能解决标识符注入。遇到动态表名、列名、排序字段时老老实实做白名单映射才是正解。3.4 哪些结果需要人工修订把模型的输出当参考答案可以当权威结论就有风险了。我复核后的结论是检测脚本需要补充编码解码和注释符处理修复代码基本可以直接用但需要看边界条件加固方案需要排优先级和区分环境。具体来说我会做的修订有三处。第一检测脚本增加一个前置预处理函数把 URL 编码解码、/**/归一化成空格、连续空白符折叠、注释符截断这些操作放在正则匹配之前能立刻减少一半以上的漏报。第二修复代码我在模拟项目里跑了一遍单元测试确认注入样本变成参数值后不会影响正常查询结构但还要补上排序字段的白名单校验逻辑。第三加固方案里我建议把“参数化查询”设为强制项把 WAF 和监控设为迭代项而不是所有措施同时上。模型生成的清单适合当核对表不适合当执行计划。我在实际工作里的习惯是让模型先生成初稿然后我自己过一遍安全和业务的双重视角再把它交给代码评审工具做一轮静态扫描。这样模型负责效率工具负责精度人负责判断各司其职。4. 常见问题与排查技巧实录4.1 模型输出不稳定怎么办用 1.5B 模型遇到最多的问题是同一段提示词跑两次结果可能不一样。这跟模型的采样参数有关系。默认情况下生成带有随机性代码模型尤其受影响因为代码对语法正确性和逻辑一致性要求极高稍微一偏差就出问题。我的解法很直接把温度参数调低。在 ollama 里可以通过 API 参数设置temperature: 0.1或者用OLLAMA_API调用时直接指定options.temperature。温度降到这个水平后输出稳定性明显提升同一个任务生成三次主体逻辑基本一致。如果想更严格可以把top_p也调低到0.8左右进一步压缩随机采样空间。还有一个小技巧在提示词里放一个期望输出的“骨架示例”。比如你希望它先输出代码再输出说明就在提示词里写一句“先给出完整 Python 脚本脚本用代码块包裹不要输出多余的前缀”。小模型对格式约束的遵循能力比大模型弱但只要你在提示词里给了明确的格式锚点它大多数时候会照做。我试过在提示词里加一个简短的参考输出样例效果立竿见影比单纯强调“请遵守格式”有效得多。4.2 正则匹配的误报和漏报这是模型生成的检测代码最大的痛点也是最需要人工介入的地方。先看误报。模型第一个正则(|\)\s*(OR|AND)\s.*?会把正常输入“用户问密码是否正确”里的和前面的词误判为注入特征。更典型的是用户 Input 里包含“--”这种符号比如商品编号“SH-023-2024”也会被直接标记为危险。误报的后果是真正的恶意请求被淹没在海量告警里时间长了运营人员会忽略所有告警等于没有防护。再看漏报。我前面验证过的/**/注释符绕过和%27编码绕过就是典型的漏报场景。攻击者可以无限变换载荷的编码形态纯正则方案永远追不上。我的经验是不要试图把正则规则写全而是分两步第一步做归一化把编码和注释符全部还原成标准形态第二步做语义判断比如用 SQL 解析器去分析参数值是否改变了语句结构。这个思路比堆正则模式靠谱得多。模型给出的检测脚本只是这个两步方案的“第一步”它能帮你快速搭出骨架但归一化函数和语义分析层得自己补。我在补充版本里加了一个简单的normalize_input函数对 URL 解码和注释符清理做预处理实测漏报率能降低一半以上。4.3 别的语言场景怎么延伸SQL 注入不只是 Java 的问题Python 的字符串拼接、Go 的fmt.Sprintf构造 SQL、Node.js 里的模板字符串都可能踩进同一个坑。我试着让模型分别针对 Python 和 Go 场景生成了检测代码它的表现同样能给出可用结果但细节上有差异。Python 场景里模型能主动指出 f-string 拼接 SQL 的风险并建议使用sqlite3或psycopg2的参数化接口。Go 场景里模型知道database/sql库的占位符是?也能给出db.QueryContext的正确用法。在模型自己的大语言模型预训练语料里Python 安全代码含量远高于 Go所以这两类质量有差距但对 1.5B 这个体量能把主流语言的典型误用模式说出来已经够用。延伸使用时有一个技巧把目标语言的典型错误代码样例直接塞进提示词里。比如测 Go 时先把一段用fmt.Sprintf拼接 SQL 的错误代码贴进去模型就知道要针对这个模式来生成检测逻辑而不是泛泛回答。我用这种方法让模型生成了 Python 版本和 Go 版本的加固清单落地实用性比第一轮空泛回答好很多。4.4 我踩过的坑和最终建议测这个模型的过程中我踩了几个值得分享的坑。第一个坑是提示词里没给格式示例结果模型输出的内容夹杂着大量 Markdown 表格、括号说明和解释文字直接拿去跑必然报错。后来我在提示词里加了“代码必须完整可用不要解释代码含义”之后输出才干净了。第二个坑是一开始用了默认温度同一个检测任务跑了三次三次的正则规则都不一样我以为模型功能有问题调低温度后就稳定了。第三个坑是在内存只有 8GB 的旧机器上试过推理速度偏慢生成一段代码要十几秒而且量化精度损失后输出质量下降明显。换到 16GB 内存的机器后体验好了很多。最终的建议是把 Qwen2.5-Coder-1.5B 当成一个靠谱的结对同事来看待它可以快速出第一版检测代码、给出修复思路、整理加固清单但交付前必须由人复核。如果你只有一台普通笔记本没有独立显卡又想试 AI 辅助安全编码它是最低门槛的选择之一。如果条件允许我建议你在本地跑一个 1.5B 的模型专门处理常规安全任务再配合商业级扫描工具做深度检测这样效率和准确性都能兼顾。最后再分享一个我在实际使用中发现的小技巧做完一轮检测任务后把模型的输出和你自己修正后的版本一起存成样本下次需要类似任务时把修正版作为提示词里的示例传给模型。你会发现模型的第二次输出质量会明显靠近你给的修正版相当于用少量人工标注把模型的能力引导到你要的方向。这个方法不花一分钱但对小模型的实用性提升是实实在在的。