aigems commited on
Commit
8a37762
·
1 Parent(s): 826a643
Files changed (3) hide show
  1. output.txt +463 -0
  2. public/js/main.js +28 -20
  3. routes/command.js +4 -4
output.txt ADDED
@@ -0,0 +1,463 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 文件名:/workspaces/hf-exec/exec/Dockerfile
2
+ 文件内容:
3
+ FROM node:20
4
+
5
+ ARG ADMIN_USERNAME=admin
6
+ ARG ADMIN_PASSWORD=password
7
+
8
+ WORKDIR /app
9
+
10
+ COPY package*.json ./
11
+
12
+ RUN npm install
13
+
14
+ COPY . .
15
+
16
+ RUN mkdir -p /app/data && chown -R node:node /app /app/data
17
+
18
+ EXPOSE 7860
19
+
20
+ ENV ADMIN_USERNAME=${ADMIN_USERNAME}
21
+ ENV ADMIN_PASSWORD=${ADMIN_PASSWORD}
22
+
23
+ CMD ["node", "app.js"]
24
+
25
+ --------------------------------------------------
26
+
27
+ 文件名:/workspaces/hf-exec/exec/package.json
28
+ 文件内容:
29
+ {
30
+ "name": "web-command-executor",
31
+ "version": "1.2.0",
32
+ "main": "app.js",
33
+ "scripts": {
34
+ "start": "node app.js",
35
+ "dev": "nodemon app.js",
36
+ "lint": "eslint .",
37
+ "test": "jest"
38
+ },
39
+ "dependencies": {
40
+ "express": "^4.17.1",
41
+ "winston": "^3.3.3",
42
+ "helmet": "^4.6.0",
43
+ "express-rate-limit": "^5.3.0",
44
+ "jsonwebtoken": "^8.5.1"
45
+ },
46
+ "devDependencies": {
47
+ "nodemon": "^2.0.7",
48
+ "eslint": "^7.32.0",
49
+ "jest": "^27.0.6"
50
+ },
51
+ "engines": {
52
+ "node": ">=18.0.0"
53
+ }
54
+ }
55
+
56
+ --------------------------------------------------
57
+
58
+ 文件名:/workspaces/hf-exec/exec/app.js
59
+ 文件内容:
60
+ const express = require('express');
61
+ const winston = require('winston');
62
+ const fs = require('fs').promises;
63
+ const path = require('path');
64
+ const rateLimit = require('express-rate-limit');
65
+ const { promises: fsPromises } = require('fs');
66
+ const helmet = require('helmet');
67
+ const jwt = require('jsonwebtoken');
68
+
69
+ const app = express();
70
+ const port = process.env.PORT || 7860;
71
+
72
+ // 配置日志记录器
73
+ const logger = winston.createLogger({
74
+ level: process.env.LOG_LEVEL || 'info',
75
+ format: winston.format.combine(
76
+ winston.format.timestamp(),
77
+ winston.format.json()
78
+ ),
79
+ transports: [
80
+ new winston.transports.File({ filename: 'error.log', level: 'error' }),
81
+ new winston.transports.File({ filename: 'combined.log' })
82
+ ]
83
+ });
84
+
85
+ if (process.env.NODE_ENV !== 'production') {
86
+ logger.add(new winston.transports.Console({
87
+ format: winston.format.simple()
88
+ }));
89
+ }
90
+
91
+ // 安全中间件
92
+ app.use(helmet());
93
+
94
+ // 速率限制
95
+ const limiter = rateLimit({
96
+ windowMs: 15 * 60 * 1000, // 15分钟
97
+ max: 100 // 每个IP限制100个请求
98
+ });
99
+ app.use('/api', limiter);
100
+
101
+ app.use(express.json());
102
+ app.use(express.static('public'));
103
+
104
+ // JWT 密钥
105
+ const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
106
+
107
+ // 中间件:验证 JWT
108
+ const authenticateJWT = (req, res, next) => {
109
+ const authHeader = req.headers.authorization;
110
+
111
+ if (authHeader) {
112
+ const token = authHeader.split(' ')[1];
113
+
114
+ jwt.verify(token, JWT_SECRET, (err, user) => {
115
+ if (err) {
116
+ return res.sendStatus(403);
117
+ }
118
+
119
+ req.user = user;
120
+ next();
121
+ });
122
+ } else {
123
+ res.sendStatus(401);
124
+ }
125
+ };
126
+
127
+ // 路由
128
+ const authRoutes = require('./routes/auth');
129
+ const commandRoutes = require('./routes/command');
130
+
131
+ app.use('/api', authRoutes);
132
+ app.use('/api', authenticateJWT, commandRoutes);
133
+
134
+ // 错误处理中间件
135
+ app.use((err, req, res, next) => {
136
+ logger.error(`未捕获的错误: ${err.message}`);
137
+ res.status(500).json({ error: '服务器内部错误' });
138
+ });
139
+
140
+ // 确保命令历史文件存在
141
+ const historyFilePath = path.join(__dirname, 'data', 'command_history.json');
142
+ fsPromises.access(historyFilePath)
143
+ .catch(() => fsPromises.writeFile(historyFilePath, '[]'))
144
+ .then(() => {
145
+ app.listen(port, () => {
146
+ logger.info(`Web 命令执行应用正在监听 http://localhost:${port}`);
147
+ });
148
+ })
149
+ .catch(err => {
150
+ logger.error('无法创建命令历史文件:', err);
151
+ process.exit(1);
152
+ });
153
+
154
+ --------------------------------------------------
155
+
156
+ 文件名:/workspaces/hf-exec/exec/utils/logger.js
157
+ 文件内容:
158
+ const winston = require('winston');
159
+
160
+ const logger = winston.createLogger({
161
+ level: process.env.LOG_LEVEL || 'info',
162
+ format: winston.format.combine(
163
+ winston.format.timestamp(),
164
+ winston.format.json()
165
+ ),
166
+ transports: [
167
+ new winston.transports.File({ filename: 'error.log', level: 'error' }),
168
+ new winston.transports.File({ filename: 'combined.log' })
169
+ ]
170
+ });
171
+
172
+ if (process.env.NODE_ENV !== 'production') {
173
+ logger.add(new winston.transports.Console({
174
+ format: winston.format.simple()
175
+ }));
176
+ }
177
+
178
+ module.exports = logger;
179
+
180
+ --------------------------------------------------
181
+
182
+ 文件名:/workspaces/hf-exec/exec/routes/command.js
183
+ 文件内容:
184
+ const express = require('express');
185
+ const { execFile } = require('child_process');
186
+ const fs = require('fs').promises;
187
+ const path = require('path');
188
+ const router = express.Router();
189
+
190
+ const logger = require('../utils/logger');
191
+
192
+ // 命令白名单
193
+ const allowedCommands = ['ls', 'pwd', 'whoami', 'date', 'echo', 'cat'];
194
+
195
+ // 历史命令文件路径
196
+ const historyFilePath = path.join(__dirname, '..', 'data', 'command_history.json');
197
+
198
+ router.get('/command-history', async (req, res) => {
199
+ try {
200
+ let history = [];
201
+ try {
202
+ const historyData = await fs.readFile(historyFilePath, 'utf-8');
203
+ history = JSON.parse(historyData);
204
+ } catch (readError) {
205
+ logger.warn('读取命令历史失败,使用空数组:', readError);
206
+ }
207
+ res.json(history);
208
+ } catch (error) {
209
+ logger.error('处理命令历史请求失败:', error);
210
+ res.status(500).json({ error: '无法读取命令历史' });
211
+ }
212
+ });
213
+
214
+ router.post('/execute', async (req, res) => {
215
+ const { command } = req.body;
216
+ const baseCommand = command.split(' ')[0];
217
+
218
+ // 白名单检查的部分
219
+ // if (!allowedCommands.includes(baseCommand)) {
220
+ // logger.warn(`用户 ${req.user.username} 尝试执行未授权的命令: ${command}`);
221
+ // return res.status(403).json({ error: '未授权的命令' });
222
+ // }
223
+
224
+ execFile(baseCommand, command.split(' ').slice(1), { timeout: 5000 }, async (error, stdout, stderr) => {
225
+ if (error) {
226
+ logger.error(`命令执行错误: ${error.message}`);
227
+ return res.status(500).json({ error: error.message });
228
+ }
229
+
230
+ // 记录命令历史
231
+ try {
232
+ let history = [];
233
+ try {
234
+ const historyData = await fs.readFile(historyFilePath, 'utf-8');
235
+ history = JSON.parse(historyData);
236
+ } catch (readError) {
237
+ // 如果文件不存在或为空,使用空数组
238
+ }
239
+
240
+ history.push({ command, timestamp: new Date().toISOString(), user: req.user.username });
241
+ if (history.length > 100) history.shift(); // 保留最近100条命令
242
+
243
+ await fs.writeFile(historyFilePath, JSON.stringify(history, null, 2));
244
+ } catch (writeError) {
245
+ logger.error('写入命令历史失败:', writeError);
246
+ }
247
+
248
+ logger.info(`用户 ${req.user.username} 成功执行命令: ${command}`);
249
+ res.json({ output: stdout, error: stderr });
250
+ });
251
+ });
252
+
253
+ module.exports = router;
254
+
255
+ --------------------------------------------------
256
+
257
+ 文件名:/workspaces/hf-exec/exec/routes/auth.js
258
+ 文件内容:
259
+ const express = require('express');
260
+ const jwt = require('jsonwebtoken');
261
+ const router = express.Router();
262
+
263
+ const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
264
+
265
+ const users = [
266
+ {
267
+ id: 1,
268
+ username: process.env.ADMIN_USERNAME,
269
+ password: process.env.ADMIN_PASSWORD
270
+ }
271
+ ];
272
+
273
+ router.post('/login', (req, res) => {
274
+ const { username, password } = req.body;
275
+ const user = users.find(u => u.username === username && u.password === password);
276
+
277
+ if (user) {
278
+ const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, { expiresIn: '1h' });
279
+ res.json({ token });
280
+ } else {
281
+ res.status(401).json({ error: '无效的用户名或密码' });
282
+ }
283
+ });
284
+
285
+ module.exports = router;
286
+
287
+ --------------------------------------------------
288
+
289
+ 文件名:/workspaces/hf-exec/exec/public/index.html
290
+ 文件内容:
291
+ <!DOCTYPE html>
292
+ <html lang="zh-CN">
293
+
294
+ <head>
295
+ <meta charset="UTF-8">
296
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
297
+ <title>Web 命令执行</title>
298
+ <script src="https://cdn.tailwindcss.com"></script>
299
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/xss.min.js"></script>
300
+ <style>
301
+ @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;700&display=swap');
302
+
303
+ body {
304
+ font-family: 'Noto Sans SC', sans-serif;
305
+ }
306
+ </style>
307
+ </head>
308
+
309
+ <body class="bg-gradient-to-r from-blue-100 to-purple-100 min-h-screen flex items-center justify-center">
310
+ <div class="max-w-2xl w-full mx-auto bg-white p-8 rounded-lg shadow-xl">
311
+ <h1 class="text-4xl font-bold mb-6 text-center text-blue-600">Web 命令执行</h1>
312
+
313
+ <div id="loginForm" class="space-y-4">
314
+ <input type="text" id="username" placeholder="用户名"
315
+ class="p-3 border rounded w-full focus:ring-2 focus:ring-blue-300 transition">
316
+ <input type="password" id="password" placeholder="密码"
317
+ class="p-3 border rounded w-full focus:ring-2 focus:ring-blue-300 transition">
318
+ <button id="loginButton"
319
+ class="w-full bg-blue-500 text-white px-4 py-3 rounded hover:bg-blue-600 transition">登录</button>
320
+ </div>
321
+
322
+ <div id="commandInterface" style="display: none;" class="space-y-6">
323
+ <p class="text-gray-600">此应用允许您在服务器上执行命令。请在下方输入命令并点击"执行"或按回车键。</p>
324
+ <div class="flex">
325
+ <input type="text" id="command" placeholder="输入命令"
326
+ class="flex-grow p-3 border rounded-l focus:outline-none focus:ring-2 focus:ring-blue-300 transition">
327
+ <button id="executeButton"
328
+ class="bg-green-500 text-white px-6 py-3 rounded-r hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-300 transition">执行</button>
329
+ </div>
330
+ <div id="loadingIndicator" class="text-center" style="display: none;">
331
+ <div class="inline-block animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500"></div>
332
+ </div>
333
+ <div id="output" class="bg-gray-100 p-4 rounded h-64 overflow-y-auto font-mono text-sm"></div>
334
+ <div>
335
+ <h2 class="text-2xl font-bold mb-3 text-blue-600">命令历史</h2>
336
+ <ul id="history" class="list-disc pl-5 space-y-2 text-gray-700"></ul>
337
+ </div>
338
+ </div>
339
+ </div>
340
+
341
+ <script src="/js/main.js"></script>
342
+ </body>
343
+
344
+ </html>
345
+
346
+ --------------------------------------------------
347
+
348
+ 文件名:/workspaces/hf-exec/exec/public/js/main.js
349
+ 文件内容:
350
+ const commandInput = document.getElementById('command');
351
+ const output = document.getElementById('output');
352
+ const history = document.getElementById('history');
353
+ const executeButton = document.getElementById('executeButton');
354
+ const loadingIndicator = document.getElementById('loadingIndicator');
355
+
356
+ commandInput.addEventListener('keypress', function (event) {
357
+ if (event.key === 'Enter') {
358
+ executeCommand();
359
+ }
360
+ });
361
+
362
+ async function executeCommand() {
363
+ const command = commandInput.value;
364
+ if (!command.trim()) return;
365
+
366
+ showLoading(true);
367
+ output.textContent = '正在执行命令...';
368
+
369
+ try {
370
+ const response = await fetch('/api/execute', {
371
+ method: 'POST',
372
+ headers: {
373
+ 'Content-Type': 'application/json',
374
+ 'Authorization': `Bearer ${localStorage.getItem('token')}`
375
+ },
376
+ body: JSON.stringify({ command })
377
+ });
378
+ const data = await response.json();
379
+ if (response.ok) {
380
+ output.textContent = data.output || data.error;
381
+ commandInput.value = '';
382
+ loadCommandHistory();
383
+ } else {
384
+ output.textContent = `错误: ${data.error}`;
385
+ }
386
+ } catch (error) {
387
+ output.textContent = '错误: ' + error.message;
388
+ } finally {
389
+ showLoading(false);
390
+ }
391
+ }
392
+
393
+ async function loadCommandHistory() {
394
+ try {
395
+ const response = await fetch('/api/command-history', {
396
+ headers: {
397
+ 'Authorization': `Bearer ${localStorage.getItem('token')}`
398
+ }
399
+ });
400
+ const data = await response.json();
401
+ history.innerHTML = '';
402
+ data.reverse().forEach(item => {
403
+ const li = document.createElement('li');
404
+ li.textContent = `${filterXSS(item.command)} (${new Date(item.timestamp).toLocaleString()})`;
405
+ li.className = 'cursor-pointer hover:text-blue-500';
406
+ li.onclick = () => {
407
+ commandInput.value = item.command;
408
+ };
409
+ history.appendChild(li);
410
+ });
411
+ } catch (error) {
412
+ console.error('加载命令历史失败:', error);
413
+ }
414
+ }
415
+
416
+ function showLoading(isLoading) {
417
+ loadingIndicator.style.display = isLoading ? 'block' : 'none';
418
+ executeButton.disabled = isLoading;
419
+ }
420
+
421
+ async function login() {
422
+ const username = document.getElementById('username').value;
423
+ const password = document.getElementById('password').value;
424
+ try {
425
+ const response = await fetch('/api/login', {
426
+ method: 'POST',
427
+ headers: { 'Content-Type': 'application/json' },
428
+ body: JSON.stringify({ username, password })
429
+ });
430
+ const data = await response.json();
431
+ if (response.ok) {
432
+ localStorage.setItem('token', data.token);
433
+ document.getElementById('loginForm').style.display = 'none';
434
+ document.getElementById('commandInterface').style.display = 'block';
435
+ loadCommandHistory();
436
+ } else {
437
+ alert('登录失败: ' + data.error);
438
+ }
439
+ } catch (error) {
440
+ alert('登录错误: ' + error.message);
441
+ }
442
+ }
443
+
444
+ document.getElementById('loginButton').addEventListener('click', login);
445
+
446
+ loadCommandHistory();
447
+
448
+ function checkLoginStatus() {
449
+ const token = localStorage.getItem('token');
450
+ if (token) {
451
+ document.getElementById('loginForm').style.display = 'none';
452
+ document.getElementById('commandInterface').style.display = 'block';
453
+ loadCommandHistory();
454
+ }
455
+ }
456
+
457
+ // 在页面加载时调用此函数
458
+ window.addEventListener('load', checkLoginStatus);
459
+
460
+ document.getElementById('executeButton').addEventListener('click', executeCommand);
461
+
462
+ --------------------------------------------------
463
+
public/js/main.js CHANGED
@@ -17,27 +17,35 @@ async function executeCommand() {
17
  showLoading(true);
18
  output.textContent = '正在执行命令...';
19
 
20
- try {
21
- const response = await fetch('/api/execute', {
22
- method: 'POST',
23
- headers: {
24
- 'Content-Type': 'application/json',
25
- 'Authorization': `Bearer ${localStorage.getItem('token')}`
26
- },
27
- body: JSON.stringify({ command })
28
- });
29
- const data = await response.json();
30
- if (response.ok) {
31
- output.textContent = data.output || data.error;
32
- commandInput.value = '';
33
- loadCommandHistory();
34
- } else {
35
- output.textContent = `错误: ${data.error}`;
 
 
 
 
 
 
 
 
 
 
 
 
36
  }
37
- } catch (error) {
38
- output.textContent = '错误: ' + error.message;
39
- } finally {
40
- showLoading(false);
41
  }
42
  }
43
 
 
17
  showLoading(true);
18
  output.textContent = '正在执行命令...';
19
 
20
+ async function executeCommand() {
21
+
22
+ try {
23
+ const response = await fetch('/api/execute', {
24
+ method: 'POST',
25
+ headers: {
26
+ 'Content-Type': 'application/json',
27
+ 'Authorization': `Bearer ${localStorage.getItem('token')}`
28
+ },
29
+ body: JSON.stringify({ command })
30
+ });
31
+ if (!response.ok) {
32
+ throw new Error(`HTTP error! status: ${response.status}`);
33
+ }
34
+ const data = await response.json();
35
+ // ... 处理成功响应 ...
36
+ } catch (error) {
37
+ console.error('执行命令时出错:', error);
38
+ output.textContent = '错误: ' + (error.message || '未知错误');
39
+ if (error.message.includes('403')) {
40
+ // 处理 Forbidden 错误
41
+ alert('访问被拒绝。请检查您的权限或重新登录。');
42
+ // 可能需要重定向到登录页面或清除token
43
+ localStorage.removeItem('token');
44
+ checkLoginStatus();
45
+ }
46
+ } finally {
47
+ showLoading(false);
48
  }
 
 
 
 
49
  }
50
  }
51
 
routes/command.js CHANGED
@@ -33,10 +33,10 @@ router.post('/execute', async (req, res) => {
33
  const baseCommand = command.split(' ')[0];
34
 
35
  // 白名单检查的部分
36
- // if (!allowedCommands.includes(baseCommand)) {
37
- // logger.warn(`用户 ${req.user.username} 尝试执行未授权的命令: ${command}`);
38
- // return res.status(403).json({ error: '未授权的命令' });
39
- // }
40
 
41
  execFile(baseCommand, command.split(' ').slice(1), { timeout: 5000 }, async (error, stdout, stderr) => {
42
  if (error) {
 
33
  const baseCommand = command.split(' ')[0];
34
 
35
  // 白名单检查的部分
36
+ if (!allowedCommands.includes(baseCommand)) {
37
+ logger.warn(`用户 ${req.user.username} 尝试执行未授权的命令: ${command}`);
38
+ return res.status(403).json({ error: '未授权的命令' });
39
+ }
40
 
41
  execFile(baseCommand, command.split(' ').slice(1), { timeout: 5000 }, async (error, stdout, stderr) => {
42
  if (error) {