中国网络渗透测试联盟
标题:
phpcms v9 2013-02-01 会员中心注入漏洞分析报告
[打印本页]
作者:
admin
时间:
2013-2-4 16:17
标题:
phpcms v9 2013-02-01 会员中心注入漏洞分析报告
报告名称:phpcms v9 2013-02-01 会员中心注入漏洞分析报告
, t2 e& C1 s2 d# U; Z b) \
漏洞作者:skysheep
" }) P& _* X, F _; F) P
分析作者:Seay
8 }) d" B3 k" _% _. F
博客:
http://www.cnseay.com/
, g8 ^- j6 p4 d' \, H0 i
漏洞分析:
3 K- I+ J6 ]. s7 S8 O; N2 m
漏洞存在于 phpcms\modules\member\index.php 文件account_manage_info函数,其功能是更新会员信息。
* T, Y3 q8 \1 G$ b
! P- a! z% R4 T. k& s
$ E$ i) J+ i. t9 o" ?
' d" h2 r' z! w# C6 N0 g- V, z
public function account_manage_info() {
& n/ D, e/ d% Y' i$ l% @
if(isset($_POST['dosubmit'])) {
\0 p' K& [* v* H5 g' k' X9 `
//更新用户昵称
- W# \5 E; N: T- ]
$nickname = isset($_POST['nickname']) && trim($_POST['nickname']) ? trim($_POST['nickname']) : '';
% I6 o8 }3 g% V9 g5 }
if($nickname) {
& c5 r# @7 p; e; q6 V5 M
$this->db->update(array('nickname'=>$nickname), array('userid'=>$this->memberinfo['userid']));
1 q* s8 s4 b- U+ L
if(!isset($cookietime)) {
7 t* e1 Y9 H8 N0 }
$get_cookietime = param::get_cookie('cookietime');
% n& g0 g8 m! X* y
}
$ B1 ?4 F; ]8 h, f9 E9 T R. T
$_cookietime = $cookietime ? intval($cookietime) : ($get_cookietime ? $get_cookietime : 0);
. g: w) f7 k% ~6 ^) o
$cookietime = $_cookietime ? TIME + $_cookietime : 0;
) h. [+ W' O9 m# A9 s1 Q7 t1 x% y
param::set_cookie('_nickname', $nickname, $cookietime);
. V0 X7 [% }0 {& J' x/ A
}
! m" |4 i) y8 G0 k! l. l9 l
require_once CACHE_MODEL_PATH.'member_input.class.php';
. x7 C+ i- J; S
require_once CACHE_MODEL_PATH.'member_update.class.php';
6 m2 z$ u6 z5 ^+ h U$ ]% t) ^
$member_input = new member_input($this->memberinfo['modelid']);
! J% \, O$ f& D5 \
$modelinfo = $member_input->get($_POST['info']);
9 `' w" I. M, d0 O& E
$this->db->set_model($this->memberinfo['modelid']);
; K& Y; U. u: F& c
$membermodelinfo = $this->db->get_one(array('userid'=>$this->memberinfo['userid']));
( b2 w9 l9 p; Q! n4 @" L) V8 u
if(!empty($membermodelinfo)) {
- }, }. f! _) G0 {! P
$this->db->update($modelinfo, array('userid'=>$this->memberinfo['userid']));
) L" z6 l" ^1 [) w% R) \* Z; U+ H
} else {
, s i: }0 \6 A/ b3 V& O8 J
$modelinfo['userid'] = $this->memberinfo['userid'];
) j2 j1 J. t6 w9 a9 n8 m$ v% l' T+ o6 s
$this->db->insert($modelinfo);
% k, a( f5 Y# t
}
# q a1 d* ]4 _; {1 v _# ]
代码中:$modelinfo = $member_input->get($_POST['info']);取得提交上来的会员模型中的字段,我们跟进member_input类中的get()函数看看,
# D( t1 K! [$ j% ^! M; C# q: F
在\caches\caches_model\caches_data\ member_input.class.php 文件中:
; |! D# V( g- {* B1 l3 _3 A6 V% U J
8 h0 P( P! k. N3 Q7 ~
) L5 {, z/ G* ^5 G! G1 c/ p+ Q
8 f' L' M# J5 S/ I4 `+ S2 ]
function get($data) {
R2 h- m6 ~0 Z: j6 R
$this->data = $data = trim_script($data);
8 {7 m" s' N- {. h+ q7 k
$model_cache = getcache('member_model', 'commons');
& r! A' w0 {0 @6 V2 I5 R9 Q
$this->db->table_name = $this->db_pre.$model_cache[$this->modelid]['tablename'];
* K) b# }& A( a6 y2 C
$info = array();
' y' m' y" ?! m- A
$debar_filed = array('catid','title','style','thumb','status','islink','description');
, H& m( ^5 H" W/ I2 {% b' W, m
if(is_array($data)) {
9 q" p$ X6 ~* X3 W
foreach($data as $field=>$value) {
. u( \4 n/ G% ]0 G
if($data['islink']==1 && !in_array($field,$debar_filed)) continue;
* s2 D! {0 {! e, q1 {$ x& T! K
$name = $this->fields[$field]['name'];
6 l: e9 v9 g3 _! | u6 y
$minlength = $this->fields[$field]['minlength'];
- b4 L+ @" w$ @, ?
$maxlength = $this->fields[$field]['maxlength'];
6 ]% m! V1 d' r2 @
$pattern = $this->fields[$field]['pattern'];
+ O' J, E/ L. D- J! M
$errortips = $this->fields[$field]['errortips'];
d+ Q8 f3 l2 l# y. w- q, b
if(empty($errortips)) $errortips = "$name 不符合要求!";
; r; e$ b. Y: [: U1 X; M
$length = empty($value) ? 0 : strlen($value);
1 F2 l2 f' U0 n; o8 \$ b
if($minlength && $length < $minlength && !$isimport) showmessage("$name 不得少于 $minlength 个字符!");
+ V2 ~& f) ?( V! k6 M7 {. k+ c
if($maxlength && $length > $maxlength && !$isimport) {
0 _: x( _3 N1 S
showmessage("$name 不得超过 $maxlength 个字符!");
* h* L) i& Q$ F* p
} else {
$ M6 w1 ^' x. g L- u
str_cut($value, $maxlength);
9 B/ q, \2 c0 j# J
}
4 I+ H7 a4 T5 i( ]6 Y& q4 n, ?
if($pattern && $length && !preg_match($pattern, $value) && !$isimport) showmessage($errortips);
6 h( [( s; t$ @- I6 Q2 B0 }$ A
if($this->fields[$field]['isunique'] && $this->db->get_one(array($field=>$value),$field) && ROUTE_A != 'edit') showmessage("$name 的值不得重复!");
; Q& q5 T/ S# C- b4 A8 e1 s
$func = $this->fields[$field]['formtype'];
8 y9 U; ]* A+ i: N0 U+ x
if(method_exists($this, $func)) $value = $this->$func($field, $value);
) V: ]9 E+ `. K( }% c" H* k& K' c
$info[$field] = $value;
+ B& V/ v% i. ~3 P R% ^& l5 G
}
3 ^# @. l( x8 T; y
}
/ p" E' K8 l) b
return $info;
& a8 y6 y, `) ]8 ^# C( @3 t4 X
}
, J: Z, x3 z- @' \' X0 u
trim_script函数是过滤XSS的,上面get函数一段代码干的事就是取提交上来的字段和值重新赋值到数组,
+ r( x/ f3 o2 I1 G% ]: I& x
) c U6 R/ I L* W. T
再到phpcms\modules\member\index.php 文件account_manage_info函数
, U2 O# Z# `' y I* J* G; G
过了get()函数之后。
: g( M: a1 P5 ]4 T
% @: D# e# @3 Q; \( E9 N
# K# z7 C, |0 j) Q6 U0 u/ ]$ n
$modelinfo = $member_input->get($_POST['info']);
( x3 h& i6 r* {* N* |" k7 }* Y
$this->db->set_model($this->memberinfo['modelid']);
' y# f0 c9 q/ w3 ~" ^8 n# @/ ^
$membermodelinfo = $this->db->get_one(array('userid'=>$this->memberinfo['userid']));
$ x& F7 P! `4 l* n* B9 |1 X
if(!empty($membermodelinfo)) {
" o( `* }1 S6 ?* X7 Z3 t
$this->db->update($modelinfo, array('userid'=>$this->memberinfo['userid']));
' W. i$ p9 Z0 v
} else {
# Z, T7 _6 V, |$ \3 ~8 M/ |
直接带入数据库,update函数我们跟进看看
3 G# ] f3 H6 U4 S& ?% X; M2 S' r
4 I& b) t- E/ B8 o1 d- t- I
6 x& |! I/ j. _. @" p2 J
public function update($data, $table, $where = '') {
0 e0 U K4 p* x/ k( P! O
if($table == '' or $where == '') {
$ @6 X" c! {' V$ ^
return false;
% l0 a& K$ q8 F7 ^- V
}
; s9 I: g* V, a4 L+ o. s
$where = ' WHERE '.$where;
2 g. K+ d9 n: d
$field = '';
* m2 w4 @( ?( }+ Q
if(is_string($data) && $data != '') {
8 y- x0 M; M( w/ b
$field = $data;
4 Q2 c5 Z/ K3 O8 L; }
} elseif (is_array($data) && count($data) > 0) {
7 A4 Z- d- K1 e9 s' M3 }
$fields = array();
% x6 k) _ a4 q2 b
foreach($data as $k=>$v) {
5 a) n) g/ n: \ P
switch (substr($v, 0, 2)) {
X! N1 Y: f6 _7 V$ K |+ S! M1 Y
case '+=':
( t! N1 }+ B: Z
$v = substr($v,2);
8 R; s8 b( ^6 ]& {( ^
if (is_numeric($v)) {
$ P4 ^: ~5 r. H: s2 o
$fields[] = $this->add_special_char($k).'='.$this->add_special_char($k).'+'.$this->escape_string($v, '', false);
" t) s9 Z; x& E) J0 R# S: y7 Y
} else {
' J7 `8 V; V: @. t& T7 W& u
continue;
5 I% U5 @) L, a. V4 \% [' ^0 T; x
}
( M M* a, f5 P) c
break;
2 n5 E& r. ^; x J+ N8 z' \
case '-=':
; o& K- h4 I# v# i2 N
$v = substr($v,2);
3 A* u% I5 J. p, A8 |4 H8 a
if (is_numeric($v)) {
8 V; A; n( \8 R, N% [( h
$fields[] = $this->add_special_char($k).'='.$this->add_special_char($k).'-'.$this->escape_string($v, '', false);
+ R8 z6 M, n: \3 {2 N( j9 b
} else {
1 N5 J% }- v# r2 l/ {' _
continue;
& V- |$ h! x! [, w. [: K1 h
}
/ G) i' m" d& {0 ]' D; Y, I
break;
- A: }& k/ R3 r$ @0 P4 c. E8 a
default:
3 Q1 C* d9 e" w+ O& q3 f& L
$fields[] = $this->add_special_char($k).'='.$this->escape_string($v);
9 S! y% [( g2 h( _( y
}
6 w* C9 u' X' h4 m, C! G
}
: O0 k& e9 Q- o& J% U
$field = implode(',', $fields);
+ K( V& q0 X; H8 k
} else {
$ e9 i5 }1 }/ A7 w* c
return false;
8 ]" l! q0 p& C! s
}
) E: p, e$ H( o# q* W" F3 C
$sql = 'UPDATE `'.$this->config['database'].'`.`'.$table.'` SET '.$field.$where;
# Z, N8 g- i; J. U. Z: K( z7 z
print_r($sql);
( |+ A) f) W$ ^0 M R- `( Q
return $this->execute($sql);
8 W' C; R3 o3 n6 g: b
}
0 O5 m/ p/ ?4 @* `; ^9 C) P9 i
从头到尾也是没有验证数据库是否存在数组中的字段,然后直接执行SQL语句。也就是说SQL语句中的字段我们可控,导致注入。
L5 l( s" f/ H( [% g
. f5 I. `2 q( o! y, b8 k6 U7 s7 v9 N
攻击测试:
. C& ~1 G4 ^9 e4 o
测试地址
http://localhost
9 q" I5 k* q" }7 R" M
注册会员seay并登陆。打开firebug工具HTML选项。修改birthday的name值为注入语句
' T0 v8 e9 d$ i) P
, T& v0 _. M, Z8 Z# y
[attach]179[/attach]
9 R& s7 q1 P5 X- S
; @6 w1 M. R% P3 a U
% S- O# P" H% M, y$ G8 s
[attach]180[/attach]
$ h1 H0 j; r4 F" ~/ K9 C/ k( V1 W& i
欢迎光临 中国网络渗透测试联盟 (https://www.cobjon.com/)
Powered by Discuz! X3.2