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