|
7 Q4 J o7 v. [. l
1.1.Sql注入攻击原理SQL注入漏洞可以说是在企业运营中会遇到的最具破坏性的漏洞之一,它也是目前被利用得最多的漏洞要学会如何防御SQL注入,首先我们要学习它的原理针对SQL注入的攻击行为可描述为通过在用户可控参数中注入SQL语法,破坏原有SQL结构,达到编写程序时意料之外结果的攻击行为。
/ D3 v6 y4 Q* _" c& H7 e; a 其成因可以归结为以下两个原因叠加造成的:程序编写者在处理应用程序和数据库交互时,使用字符串拼接的方式构造SQL语句未对用户可控参数进行足够的过滤便将参数内容拼接进入到SQL语句中注入攻击的本质,是把用户输入的数据当做代码执行。 `, R9 Q0 ]: g
这里有两个关键条件:用户能够控制输入原本程序要执行的代码,拼接了用户输入的数据1.2.Sql审计方法手动找的话,可以直接找到sqlmapper.xml文件或者直接搜索 select、update、delete、insert “String sql=”等关键词,定位SQL xml配置文件。
% w9 ]- J: A! Z+ G 如果 sql 语句中有出现 $ 进行参数拼接,则存在SQL注入风险当找到某个变量关键词有 SQL 注入风险时,可以再根据调用链找到该存在注入风险的业务逻辑代码,查看参数来源是否安全、是否有配置SQL危险参数过滤的过滤器,最终确认是否存在SQL注入。 2 G. ?4 w n& T& b9 \
以下给出可能造成sql注入攻击的关键字,审计时可根据实际情况进项查找常见SQL语句关键词关键词解释ResultSet数据库结果集的数据表,通常通过执行查询数据库的语句生成executeQuery执行查询。 $ Y q L& x/ u! \2 h
select数据库中的查询关键字insert数据库中的插入关键字update数据库中的修改关键字delete数据库中的删除关键字where数据库中的条件关键字union数据库中的联合查询关键字drop数据库中的删除数据库关键字 8 Z! d# G$ X7 t, W2 X) g; v/ O
create数据库中的创建数据库关键字count数据库中的返回匹配行数关键字java.sql.Connection与特定数据库的连接类Statement是 Java 执行数据库操作的重要接口jdbcTemplate
0 S- x% Z. A7 y$ t9 Y b 最基本的Spring JDBC模板PreparedStatement预编译的 SQL 语句的对象queryForInt数据库查询方法关键字queryForObject数据库查询方法关键字queryForMap
: |' T# b N* N& k: ~ 数据库查询方法关键字getConnection获取sql连接outfile数据库中把表数据导出关键字load_file数据库中导入数据的关键字【一一帮助安全学习,以下都是免费获取,文末有领取方式~一一】 8 H4 |/ n0 M% E
①网络安全学习路线②20份渗透测试电子书③安全攻防357页笔记④50份安全攻防面试指南⑤安全红队渗透工具包⑥网络安全必备书籍⑦100个漏洞实战案例⑧安全大厂内部教程1.3Sql注入漏洞危害1 、 攻击者可以做到 8 ?* s( P5 Z% Z- l! Y3 n' \
业务运营的所有数据被攻击对当前数据库用户拥有的所有表数据进行增、删、改、查等操作若当前数据库用户拥有file_priv权限,攻击者可通过植入木马的方式进一步控制DB所在服务器若当前数据库用户为高权限用户,攻击者甚至可以直接执行服务器命令从而通过该漏洞直接威胁整个内网系统
; g4 J4 y& \1 v/ \- W( \$ Y 2、可能对业务造成的影响① 用户信息被篡改② 攻击者偷取代码和用户数据恶意获取线上代码被非法篡改,并造成为恶意攻击者输送流量或其他利益的影响1.4Sql注入漏洞代码示例Java 代码动态构建 SQLStatement stmt =
/ d$ C3 N4 w& i null;+ m9 g i0 f: I0 U9 V! z i
7 @' N4 e0 L. ^ ResultSet rs = null;' D9 m% U; b! U, p# J I6 I+ j
; w _( q; Y6 ?2 l6 F) k6 v/ n
try{7 \4 d0 {/ ?; e$ {. @3 P7 k
2 M* u+ O- A! H$ d
String userName = ctx.getAuthenticatedUserName(); //this is a constant $ Y) e% G) e7 h2 R) C2 l/ l
String sqlString = "SELECT * FROM t_item WHERE owner=" + userName + " AND itemName=" + request.getParameter( 7 H! W+ R, l1 k8 d: f! Z: L
"itemName") + "";8 \ y9 j2 v+ P
, Q% e, J6 H+ F0 M
stmt = connection.createStatement();0 A! V" U( H# A4 f# }! O- _
+ W. U$ l$ I- Q+ d" U5 m
rs = stmt.executeQuery(sqlString);/ H4 j: e2 s% A
% R" \* c1 C) a7 I, A" e' l // ... result set handling / Z ~, k2 ~* D1 J5 L- K' |
" A& ~8 p- B! r+ Z/ Y% C
) [( e) a% e( s2 B$ c }
" B$ d! G: B' ^; R# |! M* a/ A9 a9 c% `
catch (SQLException se){
# G- f% Q) e/ U4 Y1 {; w- }& I) u2 k
// ... logging and error handling
5 l0 [4 V" n; H, I) Q+ w1 i# d' Q& {7 k: D% Z- Z, D9 r- a' ~
}& `/ E; V+ O" j/ M: r
这里将查询字符串常量与用户输入进行拼接来动态构建SQL查询命令仅当itemName不包含单引号时,这条查询语句的行为才会是正确的。 ) V7 g& K5 A3 l ~7 E
如果一个攻击者以用户名wiley发起一个请求,并使用以下条目名称参数进行查询:name OR a = a
# f* w( i' N4 X# f# @# ^9 E5 \ 那么这个查询将变成:SELECT * FROM t_item WHERE owner = wiley ( Y3 k* \* B: h0 {+ q; q) l
AND itemname = nameORa=a;; k$ p, |* \# r' p9 s
此处,额外的OR ‘a’=a’条件导致整个WHERE子句的值总为真那么,这个查询便等价于如下非常简单的查询:SELECT * FROM t_item
0 a+ Y4 p5 h' s+ W 。 @ M% e2 K K. h$ P5 ]/ [4 }9 Y4 e
这个简化的查询使得攻击者能够绕过原有的条件限制:这个查询会返回items表中所有储存的条目,而不管它们的所有者是谁,而原本应该只返回属于当前已认证用户的条目在存储过程中动态构建SQLJava代码:CallableStatement = null
% }$ {' G5 N' \& I) j( A$ r" Q% g7 @( Q/ X; s
ResultSet results = null;7 l) ^$ y* c" J& D4 X: J* [: P5 P& g
5 }6 l9 W5 P1 \/ L
try
6 A' G/ k; r) i/ M3 ^4 I, B( ]0 q0 g# u" H
{
; `1 b, X6 n: T) \2 _' Q
' j# g" M7 V6 n+ d! M& ?9 ]# t String userName = ctx.getAuthenticatedUserName(); 。
2 v1 J* W" T" H _' A //this is a constant& @9 W J+ n. ~
+ R" B5 }3 J, S7 e2 T String itemName = request.getParameter("itemName");) y$ i% q+ d: Q `" ^0 c% e7 }
/ Y& f: D* V+ I: I* h3 s
cs = connection.prepareCall(
; i+ R. F. F( w5 E9 ]6 F' B# B "{call sp_queryItem(?,?)}");4 d. h+ m' ]* D, T; S
' j0 J5 g& v9 J cs.setString(1, userName);
' s) p% I& k- J
l' n8 R# m; J7 {0 c cs.setString(2, itemName);
9 u" l7 \: r& S1 n6 K: _- G* P
. O' Y5 e4 G9 r/ h- {! d2 b results = cs.executeQuery();+ r2 m ?6 }5 r3 H# ^8 c
& m+ U* g7 ]8 Q) J! a
, P) _" c- m6 `2 C: H // ... result set handling6 J* Q% g- G; g# `* Z3 [" r
5 Z1 Z! `& m: r$ [3 S }
8 D: t h m. }2 B9 w9 u
9 F8 G0 Z9 w; ?- j6 I! O5 X catch (SQLException se)
2 A m% C( K9 R( C4 I/ i6 ~ X
0 `! o9 }& B/ i {
: O9 p; U. K6 Y2 C2 v. y5 i" L4 N7 ?/ E: \1 M
// ... logging and error handling
! l, P& H2 C& x, M, V& J+ v' N0 x! V% j
}
: j/ q. R5 D0 H7 T" P6 G: i+ g7 v; ?( ^$ Y @* d9 W6 `- c# F
SQL Server存储过程:
# J8 @. `7 c& `, `1 k
" h+ q& ~! K' u. j8 i- p& k' e CREATE PROCEDURE sp_queryItem4 \6 b. q' M0 S( X. k
6 e5 F! O+ l4 t r
@userName varchar(
1 k0 v: Z: z+ U/ B0 M 50),
" ]1 J0 W* H r$ N" T* B+ G3 h* K$ ?! w z
@itemName varchar(50)
6 q. v: f5 H' {: J: ~6 a. }1 m
# y; A( |) \+ z- n AS
3 @' ^' d- N$ q6 z! a9 C% ^5 N2 y( c$ _: F5 i% |
BEGIN
8 \+ t' F/ Q) E3 Y
; n. v. i) E6 v" i& H DECLARE @sql nvarchar(500);
( b K# ^& u6 z- ^# H& g
* y. C" a2 w( H SET @sql = SELECT * FROM t_item2 b3 \- r2 Z; \& `
( c( u0 g4 s" T WHERE owner =
/ a/ g+ W% k; L6 {5 k + @userName +4 ^+ S- ^+ F' N
( z5 w' M5 d! `3 _* X9 e w
AND itemName = + @itemName + ;
7 Y- W- g s) }- @% V- F
6 \ o" s$ R8 A" W* P" d' i: c EXEC(@sql);
1 ]' M% V! D2 d7 g$ V% m% l m" W5 x; h2 l9 U) W5 o4 N7 a( e, F6 c+ L9 N/ x
END5 N* L: B0 k+ P' v
+ H A8 b9 _( V8 N GO
0 Z$ p5 p, L( [& e3 R
5 _: y. h/ M* c4 C 在存储过程中,通过拼接参数值来构建查询字符串,和在应用程序代码中拼接参数一样,同样是有SQL注入风险的。 : w0 Z. [7 V% M2 z4 y6 A
Hibernate 动态构建 SQL/HQL原生SQL查询:String userName = ctx.getAuthenticatedUserName(); //this is a constant
! H1 K* \" i0 X3 V' H1 c0 l: m9 l* b
9 [0 {( {3 v* \% n* \" T String itemName = request.getParameter( 1 l0 @0 S0 T, v; x5 @
"itemName");3 I% H5 n5 x/ b a; {7 w0 z2 o
- H( U# w. A* X- o* y, Q
Query sqlQuery = session.createSQLQuery("select * from t_item where owner = " + userName +
+ b* T, r( x- q " and itemName = " + itemName + "");$ U& P; j z, C Q4 y# d+ k
1 x* E' }" f2 G+ B$ l( m List rs = (List) sqlQuery.list();0 {; a9 U7 ^5 f" e( t0 \
HQL查询:String userName = ctx.getAuthenticatedUserName(); $ ~2 b/ @; L h
//this is a constant! @6 j6 b3 k/ j5 [; V) i
$ ?' m- ^# R' E0 [) X
String itemName = request.getParameter("itemName");+ W7 l/ J1 `! b, V6 G. k! U
# Z6 a( C3 h# w0 o" E9 W Query hqlQuery = session.createQuery( : V+ f" a$ q6 I1 V, E
"from Item as item where item.owner = " + userName + " and item.itemName = " + itemName + ""); d) p, R) b# D' a4 l( ?
R6 r' w& Q$ w List hrs = ( : A- z; j0 ]- s( I* C2 v1 O9 l
List) hqlQuery.list();
, P1 x) [. U. x6 D7 |3 e) f `
( _* Y: {. D4 Z3 ` q# q+ p 即使是使用Hibernate,如果在动态构建SQL/HQL查询时包含了不可信输入,同样也会面临SQL/HQL注入的问题HQL代码中,session.createQuery使用HQL语句将查询到的数据存到到list集合中,需要时在拿出来使用。 - O- K8 [* z# o( g+ c
而参数中itemName是通过request.getParameter直接获取攻击者若在此处写入恶意语句,程序将恶意语句查询出来的数据存放在list集合中,再通过某处调用成功将数据显示在前台Mybatis注入分析 + G0 G1 ~7 o' N1 I0 @
Mybatis框架下易产生SQL注入漏洞的情况主要分为以下三种:1)模糊查询like例如对人员姓名检索进行模糊查询,如果考虑安全编码规范问题,其对应的SQL语句如下:Select * fromuserwhere - ~, R6 A7 [% }' W# Y
namelike%#{name}%但由于这样写程序会报错,研发人员将SQL查询语句修改如下:Select * fromuserwherenamelike%${name}%在这种情况下我们发现程序不再报错,但是此时产生了SQL语句拼接问题,如果java代码层面没有对用户输入的内容做处理势必会产生SQL注入漏洞。
6 ?7 ?8 y$ {; q3 }% l 2)in之后的参数例如对人员姓名进行同条件多值检索的时候,如当用户输入001,002,003…时,如果考虑安全编码规范问题,其对应的SQL语句如下:Select * fromnamewhereidin ( - g% H o7 c0 K4 h7 g
#{id})但由于这样写程序会报错,研发人员将SQL查询语句修改如下:Select * fromnamewhereidin (${id}) v& Q3 O9 M5 c
修改SQL语句之后,程序停止报错,但是却引入了SQL语句拼接的问题,如果没有对用户输入的内容做过滤,势必会产生SQL注入漏洞。
- k: N2 y1 ]: l" D/ s7 b 3)order by之后(重点和区分点)当根据姓名、id序号等信息用户进行排序的时候,如果考虑安全编码规范问题,其对应的SQL语句如下:Select * fromuserwherename = qihoo " @: j2 F- v; P' M! P- ^1 `1 v9 @
orderby#{id} desc但由于发布时间id不是用户输入的参数,无法使用预编译研发人员将SQL查询语句修改如下:Select * fromuserwherename = qihooorderby 2 p/ I6 w$ R+ G! H6 Y
${id} desc修改之后,程序未通过预编译,但是产生了SQL语句拼接问题,极有可能引发SQL注入漏洞1.5.实战案例-OFCMS SQL注入漏洞分析本文中使用ofcms进行SQL注入漏洞讲解,此CMS算是对新手学习代码审计比较友好的CMS。
" P! I# \: v. Z4 f! b5 L7 [ 上述为安装成功页面,如何安装CMS本章不在赘述。后台页面:http://localhost:8080/ofcms-admin/admin/index.html $ w! {+ f5 \& B& o! [' e) q
漏洞点:ofcms-admin/src/main/java/com/ofsoft/cms/admin/controller/system/SystemGeneratrController.java
- j2 C) I- V* P( O. H3 q, q
8 p7 o2 M- }& A create方法
$ H3 \* f/ R# ?& q0 e: l: d
* c- ?" q- W3 ^9 q |) z7 ^5 R6 O# A2 e$ y6 x
+ `3 H5 G: |$ v! ?" H, I! ?/ K
7 f+ h- A$ M8 W% v
/**( p) e8 K9 [# F' N7 ^
4 m- x: a" \4 a * 创建表3 F' {" T- q1 E! @
8 q H0 m' e7 o$ K */publicvoidcreate() {7 s/ ?& u4 j) G- r0 t
, G! G* M% V7 M2 k* `3 v( P
try {
, w: ^" g" {5 l. i# G/ D% h) ]; V' B' p. h5 o
String sql = getPara("sql");$ d, ?+ o* o" J2 u; {
+ O0 _! |3 s$ i7 H Db.update(sql);
9 X! Q) M, a, t) _& y* q
3 D/ i) t2 d2 p6 `% \+ h: Z rendSuccessJson();
0 |7 a3 a, F* a% o. D
2 ?. O" D- y5 e, g } * A" D2 g( V. T2 z4 s. x
catch (Exception e) {
8 m6 p* Y* ?; q+ c/ j8 H, j/ d( j g9 q8 y# n) h7 Y8 {1 |
e.printStackTrace();1 @2 J* f8 V: a) }/ T6 y
- o* P. h N# S/ B rendFailedJson(ErrorCode.get("9999"), e.getMessage()); L {8 E; X9 Q1 c6 y2 C
0 |; P3 ~4 C; T9 M! t# W
}: i% ?3 V7 x9 `* K! D/ \
: w. l) [0 W( H u% [5 W( s( i }
0 E2 g6 A" ~1 L4 w& o S1 b, R + Y" a8 L) {6 {: J
上述代码中使用getpara获取sql的参数值,并update,跟进一下getpara和update方法跳转至jfinal-3.2.jar/com/jfinal/core/controller.class。 4 l8 D/ i: o) c# G! S! Y
public String getPara(String name) {2 G/ R4 I5 ^1 M+ m
. K4 ~' O4 D3 e# v6 G
returnthis.request.getParameter(name);
0 ^! N$ _# M2 S T
9 H! m5 b- b& b9 W& Y }
1 j/ L2 i, r: n4 g 上述代码无特殊用意,就是获取参数值,继续跟进 Db.update 方法。
! i k3 Q+ f7 p& {% p% M, |1 j1 N 跳转至jfinal-3.2.jar/com/jfinal/plugin/activerecord/Db.classpublicstaticintupdate(String sql){
; w+ K3 K G4 E; _: O+ d* R! P5 n* ?7 N5 v
return MAIN.update(sql);
- D" ^$ p8 e' ?7 a! v7 c" [- g# v4 K3 ^8 q! k
} M1 N# W3 n+ V, S7 P' X, ~2 H- D
# Y/ O/ S- o1 l0 P1 q 发现调用 MAIN.update , 继续跟进跳转至jfinal-3.2.jar/com/jfinal/plugin/activerecord/DbPro.classpublicintupdate(String sql)。
$ U: G4 f6 p6 s0 e {) G$ ], h$ s+ f# h8 d
5 ?: |5 z/ V2 N( m
returnthis.update(sql, DbKit.NULL_PARA_ARRAY);# d5 \5 w; u; n5 N, n3 C
6 N4 X# y; Y& n0 U. t1 X9 i) Q3 ] }7 D, h5 {6 t3 Y: O5 [
继续跟进到最后,发现华点public int update(String sql, Object... paras) {
, ~0 I: n9 Q9 s+ }
4 Q, V2 `( u* c8 P f* K* G3 h Connection conn = 。 9 m/ U4 `7 R% u; ^
null;5 ?/ q7 N, j5 M4 V3 K
( S* I) K9 _& H8 ?' r( e int var4;0 b: `+ k6 s6 P' {
2 f2 ~! G9 y7 o; Y* L. W# N
try {
3 s" I4 f G( B" R2 O0 n) m( f0 B9 N2 l9 g+ D7 [( x' g0 F5 J# z
conn = this.config.getConnection();//连接
7 r- x1 r3 p4 s# V7 U1 J5 l
: c$ ~7 t+ T. U1 y1 v var4 = this.update(this.config, conn, sql, paras);
! O' `, Z5 U6 Z& V# [. |8 N! T# ^* T //调用update更新
% B2 o; A0 C" S* {- ~# L
, r3 V8 F4 b8 T8 p- h3 q( M. h0 L } catch (Exception var8) {
5 e, n3 k- n, I8 \
6 Q) ?+ F$ H X$ W) ~# ]2 ^ throw new ActiveRecordException(var8);
& o" P. Y9 N9 N: N. T+ A* U, F8 y- [: W" {& d
} finally {4 G) S- i* A, V+ I: }4 L: p: T
7 e9 A9 B$ V; v5 o% A/ W/ k0 s: z this.config.close(conn);" ?" y* \0 v s- i/ N
7 W" k2 Y" b* C4 i
}
5 D! ]/ { h3 J" x* j/ e; _# y5 v h7 O) k) w6 ?
- F2 I1 @& I; e4 K! P. a& j
return var4;; q [) s( E+ \/ h
" v, A S, T6 z5 `/ a. R" ]
}
3 \7 n" S& T: ]3 A5 }- Y 重点:Object…Object是所有类的基类,而 Object… 是不确定方法参数情况下的一种多态表现形式(可以传递多个参数)再继续跟进 update ,同文件代码int . ^2 ]" `( r% Z" \4 T) U7 m2 W
update(Config config, Connection conn, String sql, Object... paras)throws SQLException {
( |/ S( s+ y0 P! m5 e
; B1 N5 i; Q' k* l* t2 C; }! L PreparedStatement pst = conn.prepareStatement(sql);4 w4 j1 o3 Q6 i
( ^0 }( P, Z& ^9 ^3 R& i( W; t
config.dialect.fillStatement(pst, paras);4 K5 |3 s8 t2 Q# I8 g' v) h
+ ?/ M3 Y) X b
% |! `/ ~2 J5 n+ B" K. ` int result = pst.executeUpdate();% ?& J# P, q! d# k8 V
0 I+ ]7 q, \0 ^' m DbKit.close(pst);
/ o4 b. I( T1 Z# u" J) f! H. n8 f. G6 l4 y3 L" U1 G
return result;% W* w; ~( J* _. T3 @0 `( I
% \/ [# X- w) B( M
}
$ S: X1 y7 a* d; N 上述代码执行SQL语句,并返回结果至此,整个功能流程结束,在我们跟进的过程中,代码中无任何过滤语句,获取参数值,调用update方法更新,更新成功后返回结果。 / x+ `. j8 {+ x+ q& r; V- Q* k
漏洞验证 ) E+ u Y8 `: F, N, y2 e" E
漏洞点打上断点,网页中输入poc进行验证update of_cms_topic set topic_url=updatexml(1,concat(0x7e,(user())),0) where topic_id =
0 Z2 W6 G5 a, i; u 1   
) a' e' I5 J: [! c 根据如上截图可看出我们传入的SQL语句是被完整的接收,并未做任何过滤直接带入数据库执行,所以此处直接写入漏洞代码爆出当前数据库账户为 root上述为sqlmap工具跑出来的注入点1.6漏洞修复方法添加全局过滤器,过滤特殊字符 j. t7 b! h2 k4 A% k! x
SQLFilter.java中:
4 U# r- b2 u5 g. R/ {+ n PreparedStatement 参数化如果使用参数化查询,则在SQL语句中使用占位符表示需在运行时确定的参数值参数化查询使得SQL查询的语义逻辑被预先定义,而实际的查询参数值则等到程序运行时再确定参数化查询使得数据库能够区分SQL语句中语义逻辑和数据参数,以确保用户输入无法改变预期的SQL查询语义逻辑。 # f! ?, [( @9 x4 P) X
在Java中,可以使用java.sql.PreparedStatement来对数据库发起参数化查询在这个正确示例中,如果一个攻击者将itemName输入为name’ OR ‘a’ = ‘a,这个参数化查询将免受攻击,而是会查找一个itemName匹配name’ OR ‘a’ = a这个字符串的条目。
& ^$ X6 }( B( p; U PreparedStatement stmt = null
/ L- U2 n2 i2 y
/ o% ]" p, Q: }# x8 \) i7 A F0 V ResultSet rs = nulltry
$ u: q9 o8 _! p) @9 O8 D# N: F
3 q d: Q. r" {0 b5 L {
( @- W" I5 r, q6 u% w ?4 F
$ y" I; {( {9 G4 ` String userName = ctx.getAuthenticatedUserName();
0 I% T' d& m$ I; f+ q //this is a constantString itemName = request.getParameter("itemName");
5 A7 I9 O( @, }' w* G; ?# G* [2 ]- i' h3 K9 ^. t( w# b
// ...Ensure that the length of userName and itemName is legitimate
9 e: i2 x" w0 M! h9 _ // ...String sqlString = "SELECT * FROM t_item WHERE owner=? AND itemName=?";
1 D+ j6 Z9 D. t! W6 b( {; s: I( n; V W! b9 _
stmt = connection.prepareStatement(sqlString);; I' L& T& a. @+ c/ Y' a
+ V5 Z# w' p! {& ?) F2 ^/ j
stmt.setString( 9 v7 x/ p% @7 t0 h4 F6 |) z m% L
1, userName);3 T3 q( C' P6 z2 Q6 G( }9 Z
0 S' Z% |! u/ X! x% B4 n5 @' K( f
stmt.setString(2, itemName);
+ A4 W* `6 H5 {& k; o; _3 Q& x3 p3 O2 }2 X
rs = stmt.executeQuery();" k0 C* U( k0 A8 u: ?, h% I7 ^
& }6 t' S5 t, ^0 Y2 i, n // ... result set handling
2 m0 k! Q7 g; q/ Q3 j
- N, D0 F, X/ E) C" r }; Z1 Z9 K+ L# y" B' p) g; I( K
+ v5 R0 g" Z' [' ~
* U* ~* M) g* g [' G; E3 { catch (SQLException se); d) J4 I& ^; D; Y8 t3 c( T z; @
# G! P' X- A; x5 e; n: x0 _; ? {# J# V" V* P y9 o# J$ V
q0 c; p$ _& i0 {
// ... logging and error handling( J/ C. Y3 `8 [2 ]' i
# n* Q7 X0 V: O8 _- _1 h
}) _+ u' X( K6 g5 d2 E. r$ s3 x# K
存储过程参数化这个存储过程使用参数化查询,而未包含不安全的动态SQL构建。
" a& V! }' U- j/ h# q- @ 数据库编译此存储过程时,会生成一个SELECT查询的执行计划,只允许原始的SQL语义被执行任何参数值,即使是被注入的SQL语句也不会被执行,因为它们不是执行计划的一部分CallableStatement =
7 C2 ~" I4 I+ ^& U- Q) Q! H null
# j2 j8 h% R. f1 l/ v" w2 w; Z8 W# m
0 M9 @, g, w& Q/ j ResultSet results = null;7 a5 }7 n- u0 C- o6 h1 K5 p1 @
$ O' s' }5 L K0 ]+ T5 T! t+ B% p
try
+ n& B* k" X( u" y* y" w/ o0 L+ v4 e! c% Q# B" z" g5 y% G% C# v
{3 T! y7 Y, ]% l/ r4 l, L
" \' O2 U. l( v5 E9 [
String userName = ctx.getAuthenticatedUserName(); //this is a constant 5 t# T' C: z$ p7 d- K
String itemName = request.getParameter("itemName");3 q1 E% E2 c+ ]/ [3 d% m7 i0 b
3 k$ x$ h) w" x. N, j // ... Ensure that the length of userName and itemName is legitimate 3 S7 w2 T! C3 f' h4 M
// ...
! R6 X j& O |3 K
+ `, w. ?9 Y/ w* T9 V cs = connection.prepareCall("{call sp_queryItem(?,?)}");
# U) a# m# k0 O# U, y' A
: T% t& p1 B1 _( v4 { cs.setString(1, userName);
8 S" o' k# d& M5 d
: m& ~1 `# L3 | cs.setString(
$ L9 J- W3 k* F+ \# V0 _ 2, itemName);
- s2 i! \3 d' `9 ?" Z
# \( u0 _- `6 P; ^8 O0 k2 m results = cs.executeQuery();2 d1 A) \+ L/ G$ O. i) a$ U
$ T. T# N( Q, a
// ... result set handling
3 w' g; |- a: R" b, ^' J) K
8 u$ \1 i% T; N7 e" @/ J }
! s5 N2 ]: w# j' V3 s- \3 K& v( a+ r5 o" [ ]/ `
catch (SQLException se)0 D+ t0 v9 q- M) ] S
7 {% n X4 x1 s: M& M8 E" k {
& E* }( d" H A1 D) G5 f |$ T8 k- b
. W8 i/ J+ T3 t! x+ x' s8 y
// ... logging and error handling
" l8 r' |* e$ e; T7 q
$ C6 }) @; V8 Y5 z/ U/ ^1 s5 t }
8 ^; V [ C; u; b$ k+ C Hibernate 参数化查询Hibernate支持SQL/HQL参数化查询为了防止SQL注入以及改善性能,以上这些示例使用了参数化绑定 的方式来设置查询参数。
& _- v" \- y& C: H String userName = ctx.getAuthenticatedUserName(); //this is a constant
4 y. U* U# Z8 Q% Z- [6 ?( @2 _8 @! `. d
String itemName = request.getParameter( ! k5 I) q% ^; N2 z, b$ o
"itemName");
5 e- M* ]% C) X2 y1 n5 |; Z1 M1 z ^' ^. w9 f& l
Query hqlQuery = session.createQuery("from Item as item where item.owner = ? and item.itemName = ?"
! O# w) r% W2 A; {/ I2 N );( m! R) V/ Z, E
5 E. N6 F* N" R hqlQuery.setString(1, userName);! M. e0 W$ @" @) G1 i
! z+ m, f4 g4 N9 c& B
hqlQuery.setString(2, itemName);. q( z5 r0 [% q+ }
# [1 }+ L1 I5 l: j
List rs = (List) hqlQuery. - }( h) r; I9 S
list();
8 |" F3 _. w$ O+ }2 A/ u HQL基于名称的参数化查询String userName = ctx.getAuthenticatedUserName(); //this is a constant
! c9 X7 e% x$ R$ X! b( [
" |% i! A# F7 u/ G# V8 ~ String itemName = request.getParameter(
/ Z0 j1 K" o# B4 i( A "itemName");
/ s1 X" C* }# K- l: v$ X) M; G5 n+ F' q N9 `9 y
Query hqlQuery = session.createQuery("from Item as item where item.owner = wner and item.itemName = :itemName" ( M3 U' E' f: C+ T+ `
);
( q2 l: ^- o7 S5 i4 M4 r' g, z# T
P5 \$ A! g4 J hqlQuery.setString("owner", userName);7 C+ ]0 z5 I7 C- f$ G- D0 n
7 ?: Y; G7 E9 k! V hqlQuery.setString("itemName", itemName);
3 c0 Y1 _( G Z" a6 \4 c) x7 V8 Z5 z/ r8 y# D+ Y
List rs = (
% A. M( |; {9 b3 H/ {5 l List) hqlQuery.list();8 b( W2 l# _+ _9 s; V4 V* h6 b
原生参数化查询String userName = ctx.getAuthenticatedUserName(); //this is a constant
3 c" T; n" m* |3 s: r: r& ~8 ~ 3 U; W3 p0 V% _8 D ?" T
3 C- ? z( c9 Y4 N0 r String itemName = request.getParameter("itemName");9 q/ `- g7 D& s. f6 q
5 k* U$ t3 J" E) b
Query sqlQuery = session.createSQLQuery("select * from t_item where owner = ? and itemName = ?" ( R* V% p& Z4 E4 j
);. T/ F' ]6 D1 C6 x# P3 P
7 V# l, [: E( m9 }. s
sqlQuery.setString(0, owner);
7 g [+ ]) i/ r) i7 J/ d0 }
6 N0 u2 c* h3 Q- ~9 @/ o0 E sqlQuery.setString(1, itemName);
- x+ } \: ~9 c7 @# v6 i0 Y2 W
$ S% s- {8 F5 b List rs = (List) sqlQuery.
% R- P1 k- e* X3 Z) B list();7 Z8 B3 j- C7 u; o! j8 ^( w" q& A8 H
MyBatis框架的修复方案尽量使用#描述参数,如果一定要使用$,则需要自己过滤用户输入模糊查询like SQL注入修复建议按照新闻标题对新闻进行模糊查询,可将SQL查询语句设计如下:select - X0 L; Z! P% v
* from news wherenamelikeconcat(‘%’,#{name }, ‘%’)采用预编译机制,避免了SQL语句拼接的问题,从根源上防止了SQL注入漏洞的产生in之后的参数SQL注入修复建议。 $ M# a# H2 L& H/ w, g; O- Z8 u
在对新闻进行同条件多值查询的时候,可使用Mybatis自带循环指令解决SQL语句动态拼接的问题:select * from news whereidin
9 b U/ x- E% ~0 C ^4 `8 ]9 m item="item"open="("separator=","close=")">#{item} order by SQL注入修复建议在Java层面做映射预编译机制只能处理查询参数,其他地方还需要研发人员根据具体情况来解决。 / u, |$ a- J+ P
如前面提到的排序情景:Select * from news where title =‘淘宝’ orderby#{time} asc,这里time不是查询参数,无法使用预编译机制,只能这样拼接:Select
# K' ?8 D3 C( ^7 r- I ~ * from news where title =‘淘宝’ orderby ${time} asc针对这种情况研发人员可以在java层面做映射来进行解决如当存在发布时间time和点击量click两种排序选择时,我们可以限制用户只能输入1和2。
. e8 {( E. ?( u: D1 Z 当用户输入1时,我们在代码层面将其映射为time,当用户输入2时,将其映射为click。而当用户输入1和2之外的其他内容时,我们可以将其转换为默认排序选择time(或者click)。 9 E4 B- Q# A8 D4 }# V; ]( Q
C g* {; ~8 ?2 M @( x
8 {7 L% z& b0 M& ` k* U3 @" {! `! `7 F, ?# A
5 j/ P" d7 ^' i! H; ]# b |