YXcms漏洞分析
YXcms安装
在安装该cms之前,同样也是要为其创建数据库
继续常规操作在phpstudy中添加站点,最后我们访问yxcms.com
即可进入安装界面,在数据安装界面的配置信息如下
最后安装完成主页面如下
接着我们访问后台页面,http://yxcms.com/index.php?r=admin,账号密码/admin:123456,我们就进入了后台
路由
拿到这个站点之后,我们就需要熟悉这个站点的路由方式
http://yxcms.com/index.php?r=default/column/index&col=demoshow
我们在其主控制器中编写如下测试代码
根据其之前的路由方式,我们这里访问http://yxcms.com/index.php?r=default/index/test1
,输出我们的测试内容
参数传递
修改我们的test代码,使用$_GET函数接收参数,但是我们使用常规的参数传递方式显示方法不存在
这个框架中把参数传递的符号修改为了&
如果我们传递参数的是否传入了特殊字符,使用$_GET是没有过滤的
我们在使用$_GET这种原始的方法传递参数,然后使用框架中的in()函数即可进行过滤
我们简单分析一下in函数
function in($data,$force=false){
if(is_string($data)){
$data=trim(htmlspecialchars($data));//防止被挂马,跨站攻击
if(($force==true)||(!get_magic_quotes_gpc())) {
$data = addslashes($data);//防止sql注入
}
return $data;
} else if(is_array($data)) {
foreach($data as $key=>$value){
$data[$key]=in($value,$force);//这里又遇到了和之前相同的问题,没有过滤key
}
return $data;
} else {
return $data;
}
}
这里我们还是发现了和之前一样的问题,对数组进行了处理,但是没有对key值进行过滤,我们可以看到如下的测试结果
增删改查
查询数据
在进行增删改查之前我们需要新建一个测试的数据库表yx_user
并写入如下数据
接着我们在控制器中添加测试代码,但是我们在页面访问该功能点的时候提示userModel模型类不存在
原因是我们在操作的时候是对User数据库进行操作的,但是model文件夹中并没有对应的文件
这里我们就根据其他文件的格式针对user表创建一个model文件
我们再次访问该功能点,成功查询出数据
插入数据
我们再编写插入数据的代码进行测试,这里显示的乱码但是数据成功插入
修改数据
删除数据
内核分析
查询数据内核分析
首先我们对测试代码的查询部分下断点
model层都是一些复制操作,我们这里直接分析find函数,这里我们可以看到,他一次进入了table、field、where、order、find
首先我们进入table,这里进行了赋值操作,并根据情况添加表前缀yx
table走完之后进入了find,find是查询一条,所以限制了limit=1,然后还是调用了select语句
继续走到select语句中,获得表以及字段,然后进入了一个条件函数,这里我们跟进
继续F7跟进
这里我们可以看到这里的代码主要是对condition进行拼接,而且没有进行过滤
//解析查询条件
public function parseCondition($options) {
$condition = "";
if(!empty($options['where'])) {
$condition = " WHERE ";
if(is_string($options['where'])) {
$condition .= $options['where'];
} else if(is_array($options['where'])) {
foreach($options['where'] as $key => $value) {
$condition .= " `$key` = " . $this->escape($value) . " AND ";
}
$condition = substr($condition, 0,-4);
} else {
$condition = "";
}
}
if( !empty($options['group']) && is_string($options['group']) ) {
$condition .= " GROUP BY " . $options['group'];
}
if( !empty($options['having']) && is_string($options['having']) ) {
$condition .= " HAVING " . $options['having'];
}
if( !empty($options['order']) && is_string($options['order']) ) {
$condition .= " ORDER BY " . $options['order'];
}
if( !empty($options['limit']) && (is_string($options['limit']) || is_numeric($options['limit'])) ) {
$condition .= " LIMIT " . $options['limit'];
}
if( empty($condition) ) return "";
return $condition;
}
接着回到_parseCondition
进行了赋值操作,然后返回了conditon,最终又回到了select函数中,我们继续跟进query
query具体操作
public function query($sql, $params = array(), $is_query = false) {
if ( empty($sql) ) return false;
$sql = str_replace('{pre}', $this->pre, $sql); //表前缀替换
$this->sql = $sql;//sql语句赋值
//判断当前的sql是否是查询语句
if ( $is_query || stripos(trim($sql), 'select') === 0 ) {
$data = $this->_readCache();
if ( !empty($data) ) return $data;
$query = $this->db->query($this->sql, $params); //这里的query跟进如下
while($row = $this->db->fetchArray($query)) {
$data[] = $row;
}
$this->_writeCache($data);
return $data;
} else {
return $this->db->execute($this->sql, $params); //不是查询条件,直接执行
}
}
在之前的query的操作中,又进行了db->query()操作,这里跟进结果如下,在这个操作中没有进行过滤直接进行了查询操作
插入数据内核分析
在测试代码中下断点F7跟进,首先我们会进入model层,这里我们之前已经见到,直接F8,
model层结束后,我们进入了insert函数,依次执行了table、data、insert我们F7跟进
首先我们会进入table函数,这里我们之前在分析查询的时候也已经遇到过了,过了table函数后我们会进行insert函数,这里我们进入_parseData
进行分析
在_parseData
中有调用了parseData,我们继续F7跟进
在parseData中判断了数据是否为数组,根据type进行不同的操作,这里还调用了escape函数,我们跟进分析一下
//解析待添加或修改的数据
public function parseData($options, $type) {//options中包含我们传入的数据,type是add
//如果数据是字符串,直接返回
if(is_string($options['data'])) {
return $options['data'];
}
if( is_array($options) && !empty($options) ) {//判断是否为数组
switch($type){
case 'add':
$data = array();
$data['fields'] = array_keys($options['data']);
$data['values'] = $this->escape( array_values($options['data']) );//这里调用了escape函数
return " (`" . implode("`,`", $data['fields']) . "`) VALUES (" . implode(",", $data['values']) . ") ";
case 'save':
$data = array();
foreach($options['data'] as $key => $value) {
$data[] = " `$key` = " . $this->escape($value);
}
return implode(',', $data);
default:return false;
}
}
return false;
}
escape函数中有一处过滤,过滤了数组中的value值,我们一直F8,最后返回到了_parseData
中,这时$data是拼凑过的sql语句
public function escape($value) {
if( isset($this->_readLink) ) {
$link = $this->_readLink;
} elseif( isset($this->_writeLink) ) {
$link = $this->_writeLink;
} else {
$link = $this->_getReadLink();
}
if( is_array($value) ) {
return array_map(array($this, 'escape'), $value);
} else {
if( get_magic_quotes_gpc() ) {
$value = stripslashes($value);
}
return "'" . mysql_real_escape_string($value, $link) . "'";
//mysql_real_escape_string — 转义 SQL 语句中使用的字符串中的特殊字符
}
}
继续走我们会回到insert函数中,在insert的后续操作中,首先进行了sql语句的拼接,完整的sql语句最后存储到了$this->sql中,接下来的代码就是执行sql语句,最后完成sql插入的操作,其中在execute执行了sql语句,依旧没有进行过滤
总结
如果我们传入数据的时候是数组类型的,其会过滤value值但是不会过滤key值,如果使用了in函数,也会对key值进行过滤。
数字型注入
首先我们编写测试代码,具体内容如下,关键点就是在使用find函数的时候,没有对id加单引号
首先我们进行正常请求
接着我们添加上and关键字,这里可以看到,其接收到id的内容为1 and 0
,数据库也没有查询结果
修改请求语句为id=1 and 3
,返回的结果为id=1
的请求结果,说明存在数字型注入
原因就是in函数过滤的时候仅仅过滤了字符型没有过滤数字型
insert注入
首先还是编写测试代码
进行正常请求的测试
接着我们在请求数据中修改某个数据的键名,给其添加特殊字符,我们可以看到直接报错了
这里我们构造一个报错注入
data[username`,`password`)%20VALUES%20(%27xxx%27,1%20and%20updatexml(1,concat(0x3a,user()),1)%20);%23%23%23----)%20]=aaa&data[password]=999
我们就针对这个注入进行下断分析
首先进入in中,我们可以根据之前的代码分析知道in中对value值进行了过滤没有对key值进行过滤
接着进入insert->_parseData->parseData中仅仅对value进行了过滤,最终导致注入成功
任意文件删除漏洞
对应的文件为photoController.php,在protectedappsadmincontroller下。第355行的delpic()方法
这里方便测试我们在代码中输出路径信息
echo $path.$picname;
exit();
当我们传输如下post数据的时候,我们路径信息如下,可以完成目录穿越
接着我们删除exit();
,并在C盘根目录下创建一个测试文件test.txt
,当我们执行再次执行该命令后,C盘下文件就已经被删除
根本原因就是没有对我们传输的数据进行过滤和判断,利用这个漏洞删除如下文件会导致站点重装
picname=../../protected/apps/install/install.lock
任意文件写入漏洞
对应的文件为setController.php,在protectedappsadmincontroller下。tpadd方法在140行
其代码内容如下,经过简单的分析得知,我们可以写任意内容php文件
public function tpadd()
{
$tpfile=$_GET['Mname'];
if(empty($tpfile)) $this->error('非法操作~');
$templepath=BASE_PATH . $this->tpath.$tpfile.'/';
if($this->isPost()){
$filename=trim($_POST['filename']);
$code=stripcslashes($_POST['code']);
if(empty($filename)||empty($code)) $this->error('文件名和内容不能为空');
$filepath=$templepath.$filename.'.php';
if($this->ifillegal($filepath)) {$this->error('非法的文件路径~');exit;}
try{
file_put_contents($filepath, $code);
} catch(Exception $e) {
$this->error('模板文件创建失败!');
}
$this->success('模板文件创建成功!',url('set/tplist',array('Mname'=>$tpfile)));
}else{
$this->tpfile=$tpfile;
$this->display();
}
}
首先我们添加部分测试用代码
echo $filepath;
exit();
接着在前台访问页面
我们输入文件名编写文件内容,创建并抓包,我们可以看到返回的文件路径,我们可以知道1是其创建的文件夹名称
通过返回的文件路径,且在view文件夹下包含两个文件夹default、mobile
,我们在default目录下写一个phpinfo文件,如果我们不使用default,写入文件就会失败,因为他不会创建文件夹
访问我们写入的php文件
XSS漏洞
我们在留言本提交如下内容
在后台中我们可以看到已经被过滤了
但是我们使用如下代码进行测试
我们插入的内容如下,没有进行弹框
当我们点击编辑查看后,还是会弹框
在数据库中我们可以看到,他已经被过滤了
但是到再次编辑后,将其显示在前端页面后就解析为正常的标签