Delphi开发的桌面应用如果直接拼接用户输入的内容到SQL语句中,会存在严重的SQL注入风险。比如用户在输入框中输入单引号配合额外的SQL逻辑,就可能改变原有查询的语义,导致未授权数据访问或者数据被篡改。使用TADOQuery的参数对象可以将用户输入作为参数传递,而不是直接拼接到SQL字符串中,从根源上避免注入问题。

SQL注入漏洞的产生原因
很多开发者在编写查询逻辑时,习惯用字符串拼接的方式构造SQL语句。比如下面的代码就是典型的危险写法:
procedure TForm1.BtnQueryClick(Sender: TObject); var SQLStr: string; begin SQLStr := 'SELECT * FROM user_info WHERE user_name = ''' + Edit1.Text + ''''; ADOQuery1.Close; ADOQuery1.SQL.Clear; ADOQuery1.SQL.Add(SQLStr); ADOQuery1.Open; end;
如果用户在Edit1中输入admin' OR '1'='1,拼接后的SQL会变成SELECT * FROM user_info WHERE user_name = 'admin' OR '1'='1',这个条件永远成立,会返回所有用户数据,造成信息泄露。
使用TADOQuery参数对象修复漏洞
步骤1:构造带参数占位符的SQL语句
在SQL语句中使用:加上参数名作为占位符,而不是直接拼接用户输入。修改后的SQL构造逻辑如下:
procedure TForm1.BtnQueryClick(Sender: TObject);
begin
ADOQuery1.Close;
ADOQuery1.SQL.Clear;
// 使用:user_name作为占位符
ADOQuery1.SQL.Add('SELECT * FROM user_info WHERE user_name = :user_name');
// 后续给参数赋值
end;
步骤2:给参数对象赋值
TADOQuery会自动识别SQL中的参数占位符,我们可以通过Parameters属性给对应参数赋值,赋值时会自动做类型转义和过滤,避免注入:
procedure TForm1.BtnQueryClick(Sender: TObject);
begin
ADOQuery1.Close;
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('SELECT * FROM user_info WHERE user_name = :user_name');
// 给参数赋值,直接传入用户输入的内容即可
ADOQuery1.Parameters.ParamByName('user_name').Value := Edit1.Text;
ADOQuery1.Open;
end;
多参数的使用场景
如果查询条件有多个,只需要添加多个参数占位符,分别赋值即可:
procedure TForm1.BtnQueryClick(Sender: TObject);
begin
ADOQuery1.Close;
ADOQuery1.SQL.Clear;
// 两个参数占位符
ADOQuery1.SQL.Add('SELECT * FROM user_info WHERE user_name = :user_name AND age > :min_age');
// 分别给两个参数赋值
ADOQuery1.Parameters.ParamByName('user_name').Value := Edit1.Text;
ADOQuery1.Parameters.ParamByName('min_age').Value := StrToInt(Edit2.Text);
ADOQuery1.Open;
end;
注意事项
- 不要在SQL拼接完成后再尝试替换参数,必须保证SQL中的用户输入部分全部用参数占位符代替,不能有任何拼接的用户输入内容。
- 参数赋值时要保证类型匹配,比如数值类型的参数要传入数值,避免类型转换错误。
- 如果是执行增删改操作,同样可以使用参数化方式,比如插入数据的SQL可以写成
INSERT INTO user_info (user_name, age) VALUES (:name, :age),再给对应参数赋值即可。 - 如果使用的是Access数据库,参数占位符可以用
?,但是用命名参数:参数名的可读性更好,不容易出错。
参数化查询是目前防御SQL注入最有效的方式,除了TADOQuery,Delphi中的其他数据访问组件比如TFDQuery也支持类似的参数化用法,核心逻辑都是将用户输入和SQL逻辑分离。