@sun-asterisk/sunlint 1.3.18 → 1.3.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/rules/enhanced-rules-registry.json +77 -18
- package/core/cli-program.js +2 -1
- package/core/github-annotate-service.js +89 -0
- package/core/output-service.js +25 -0
- package/core/summary-report-service.js +30 -30
- package/package.json +3 -2
- package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +392 -280
- package/rules/common/C017_constructor_logic/analyzer.js +137 -503
- package/rules/common/C017_constructor_logic/config.json +50 -0
- package/rules/common/C017_constructor_logic/symbol-based-analyzer.js +463 -0
- package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +463 -21
- package/rules/security/S011_secure_guid_generation/README.md +255 -0
- package/rules/security/S011_secure_guid_generation/analyzer.js +135 -0
- package/rules/security/S011_secure_guid_generation/config.json +56 -0
- package/rules/security/S011_secure_guid_generation/symbol-based-analyzer.js +609 -0
- package/rules/security/S028_file_upload_size_limits/README.md +537 -0
- package/rules/security/S028_file_upload_size_limits/analyzer.js +202 -0
- package/rules/security/S028_file_upload_size_limits/config.json +186 -0
- package/rules/security/S028_file_upload_size_limits/symbol-based-analyzer.js +530 -0
- package/rules/security/S041_session_token_invalidation/README.md +303 -0
- package/rules/security/S041_session_token_invalidation/analyzer.js +242 -0
- package/rules/security/S041_session_token_invalidation/config.json +175 -0
- package/rules/security/S041_session_token_invalidation/regex-based-analyzer.js +411 -0
- package/rules/security/S041_session_token_invalidation/symbol-based-analyzer.js +674 -0
- package/rules/security/S044_re_authentication_required/README.md +136 -0
- package/rules/security/S044_re_authentication_required/analyzer.js +242 -0
- package/rules/security/S044_re_authentication_required/config.json +161 -0
- package/rules/security/S044_re_authentication_required/regex-based-analyzer.js +329 -0
- package/rules/security/S044_re_authentication_required/symbol-based-analyzer.js +537 -0
- package/rules/security/S045_brute_force_protection/README.md +345 -0
- package/rules/security/S045_brute_force_protection/analyzer.js +336 -0
- package/rules/security/S045_brute_force_protection/config.json +139 -0
- package/rules/security/S045_brute_force_protection/symbol-based-analyzer.js +646 -0
- package/rules/common/C017_constructor_logic/semantic-analyzer.js +0 -340
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
# S028 - Limit Upload File Size and Number of Files Per User
|
|
2
|
+
|
|
3
|
+
## 📋 Tổng quan
|
|
4
|
+
|
|
5
|
+
**Rule ID:** S028
|
|
6
|
+
**Mức độ:** Medium (⚠️)
|
|
7
|
+
**Danh mục:** Security
|
|
8
|
+
**OWASP:** A04:2021 - Insecure Design
|
|
9
|
+
**CWE:** CWE-400 - Uncontrolled Resource Consumption
|
|
10
|
+
|
|
11
|
+
### Mục đích
|
|
12
|
+
|
|
13
|
+
Ngăn chặn các cuộc tấn công **Denial of Service (DoS)** và lạm dụng tài nguyên bằng cách đảm bảo rằng mọi file upload endpoint đều có giới hạn:
|
|
14
|
+
|
|
15
|
+
- **Kích thước file** (file size limit)
|
|
16
|
+
- **Số lượng file** (file count limit)
|
|
17
|
+
- **Tổng kích thước request** (request size limit)
|
|
18
|
+
|
|
19
|
+
### Tại sao quan trọng?
|
|
20
|
+
|
|
21
|
+
1. **Ngăn chặn DoS attacks**: Attacker có thể upload file cực lớn (vài GB) để làm cạn kiệt:
|
|
22
|
+
|
|
23
|
+
- Disk space
|
|
24
|
+
- Memory
|
|
25
|
+
- Network bandwidth
|
|
26
|
+
- Processing power
|
|
27
|
+
|
|
28
|
+
2. **Ngăn storage abuse**: User có thể upload hàng nghìn file nhỏ để chiếm dụng storage
|
|
29
|
+
|
|
30
|
+
3. **Bảo vệ service availability**: Đảm bảo server không bị quá tải bởi upload requests
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 🎯 Phạm vi áp dụng
|
|
35
|
+
|
|
36
|
+
### Framework hỗ trợ:
|
|
37
|
+
|
|
38
|
+
- ✅ **Node.js**: multer, express
|
|
39
|
+
- ✅ **NestJS**: FileInterceptor, FilesInterceptor
|
|
40
|
+
- ✅ **Java/Spring Boot**: multipart configuration
|
|
41
|
+
- ⚠️ **Python/Flask**: (Future support)
|
|
42
|
+
- ⚠️ **PHP**: (Future support)
|
|
43
|
+
|
|
44
|
+
### File types:
|
|
45
|
+
|
|
46
|
+
- `.ts`, `.js`, `.tsx`, `.jsx` (TypeScript/JavaScript)
|
|
47
|
+
- `.java` (Java - properties files)
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 🚨 Các vi phạm phát hiện
|
|
52
|
+
|
|
53
|
+
### 1. Multer không có limits (HIGH)
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
// ❌ VIOLATION: Multer thiếu giới hạn hoàn toàn
|
|
57
|
+
const upload = multer({ dest: "uploads/" });
|
|
58
|
+
|
|
59
|
+
// ❌ VIOLATION: Multer thiếu limits object
|
|
60
|
+
const upload = multer({
|
|
61
|
+
storage: diskStorage({ destination: "./uploads" }),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ❌ VIOLATION: Multer có limits nhưng thiếu fileSize
|
|
65
|
+
const upload = multer({
|
|
66
|
+
limits: {
|
|
67
|
+
files: 5, // Chỉ giới hạn số lượng, không giới hạn size
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Severity:** ERROR
|
|
73
|
+
**Message:** "Multer configuration missing size limits - add limits.fileSize and limits.files"
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### 2. File size limit quá cao (HIGH/MEDIUM)
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
// ❌ HIGH RISK: File size > 50MB
|
|
81
|
+
const upload = multer({
|
|
82
|
+
limits: {
|
|
83
|
+
fileSize: 100 * 1024 * 1024, // 100MB - QUÁ CAO!
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// ⚠️ MEDIUM RISK: File size > 20MB
|
|
88
|
+
const upload = multer({
|
|
89
|
+
limits: {
|
|
90
|
+
fileSize: 30 * 1024 * 1024, // 30MB - CAO HƠN KHUYẾN NGHỊ
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Severity:** ERROR (> 50MB), WARNING (> 20MB)
|
|
96
|
+
**Message:** "File size limit too high (100MB) - recommend ≤ 10MB"
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### 3. FileInterceptor thiếu limits (HIGH)
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// ❌ VIOLATION: FileInterceptor không có limits
|
|
104
|
+
@Post('/upload')
|
|
105
|
+
@UseInterceptors(FileInterceptor('file'))
|
|
106
|
+
uploadFile(@UploadedFile() file: Express.Multer.File) {
|
|
107
|
+
return this.fileService.save(file);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ❌ VIOLATION: FilesInterceptor không có limits
|
|
111
|
+
@Post('/gallery')
|
|
112
|
+
@UseInterceptors(FilesInterceptor('images'))
|
|
113
|
+
uploadGallery(@UploadedFiles() files: Array<Express.Multer.File>) {
|
|
114
|
+
return this.galleryService.save(files);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Severity:** ERROR
|
|
119
|
+
**Message:** "FileInterceptor missing 'limits' configuration - add { limits: { fileSize: 10485760 } }"
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### 4. Express middleware thiếu body limit (MEDIUM)
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
// ⚠️ WARNING: express.json() không giới hạn
|
|
127
|
+
app.use(express.json());
|
|
128
|
+
|
|
129
|
+
// ⚠️ WARNING: express.urlencoded() không giới hạn
|
|
130
|
+
app.use(express.urlencoded({ extended: true }));
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Severity:** WARNING
|
|
134
|
+
**Message:** "express.json() missing body size limit - add { limit: '10mb' }"
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## ✅ Cách sửa đúng
|
|
139
|
+
|
|
140
|
+
### 1. Multer với limits hợp lý
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
// ✅ GOOD: Multer với limits đầy đủ
|
|
144
|
+
const upload = multer({
|
|
145
|
+
dest: "uploads/",
|
|
146
|
+
limits: {
|
|
147
|
+
fileSize: 10 * 1024 * 1024, // 10MB
|
|
148
|
+
files: 5, // Tối đa 5 files
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ✅ GOOD: Multer với fileFilter validation
|
|
153
|
+
const upload = multer({
|
|
154
|
+
storage: diskStorage({
|
|
155
|
+
destination: "./uploads",
|
|
156
|
+
filename: (req, file, cb) => {
|
|
157
|
+
const uniqueName = `${Date.now()}-${file.originalname}`;
|
|
158
|
+
cb(null, uniqueName);
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
limits: {
|
|
162
|
+
fileSize: 5 * 1024 * 1024, // 5MB per file
|
|
163
|
+
files: 3, // Max 3 files
|
|
164
|
+
},
|
|
165
|
+
fileFilter: (req, file, cb) => {
|
|
166
|
+
// Additional validation
|
|
167
|
+
const allowedTypes = ["image/jpeg", "image/png"];
|
|
168
|
+
if (allowedTypes.includes(file.mimetype)) {
|
|
169
|
+
cb(null, true);
|
|
170
|
+
} else {
|
|
171
|
+
cb(new Error("Invalid file type"), false);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
### 2. NestJS FileInterceptor với limits
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// ✅ GOOD: FileInterceptor với limits
|
|
183
|
+
@Post('/avatar')
|
|
184
|
+
@UseInterceptors(FileInterceptor('file', {
|
|
185
|
+
limits: {
|
|
186
|
+
fileSize: 5 * 1024 * 1024 // 5MB
|
|
187
|
+
}
|
|
188
|
+
}))
|
|
189
|
+
uploadAvatar(@UploadedFile() file: Express.Multer.File) {
|
|
190
|
+
return this.userService.updateAvatar(file);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ✅ GOOD: FilesInterceptor với limits
|
|
194
|
+
@Post('/documents')
|
|
195
|
+
@UseInterceptors(FilesInterceptor('documents', 10, {
|
|
196
|
+
limits: {
|
|
197
|
+
fileSize: 10 * 1024 * 1024, // 10MB per file
|
|
198
|
+
files: 10 // Max 10 files (also in decorator)
|
|
199
|
+
}
|
|
200
|
+
}))
|
|
201
|
+
uploadDocuments(@UploadedFiles() files: Array<Express.Multer.File>) {
|
|
202
|
+
return this.documentService.save(files);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ✅ GOOD: FileFieldsInterceptor với limits
|
|
206
|
+
@Post('/profile')
|
|
207
|
+
@UseInterceptors(FileFieldsInterceptor([
|
|
208
|
+
{ name: 'avatar', maxCount: 1 },
|
|
209
|
+
{ name: 'documents', maxCount: 5 }
|
|
210
|
+
], {
|
|
211
|
+
limits: {
|
|
212
|
+
fileSize: 5 * 1024 * 1024
|
|
213
|
+
}
|
|
214
|
+
}))
|
|
215
|
+
updateProfile(
|
|
216
|
+
@UploadedFiles() files: { avatar?: Express.Multer.File[], documents?: Express.Multer.File[] }
|
|
217
|
+
) {
|
|
218
|
+
return this.profileService.update(files);
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### 3. Express middleware với body limit
|
|
225
|
+
|
|
226
|
+
```javascript
|
|
227
|
+
// ✅ GOOD: express.json với limit
|
|
228
|
+
app.use(express.json({ limit: "10mb" }));
|
|
229
|
+
|
|
230
|
+
// ✅ GOOD: express.urlencoded với limit
|
|
231
|
+
app.use(
|
|
232
|
+
express.urlencoded({
|
|
233
|
+
limit: "10mb",
|
|
234
|
+
extended: true,
|
|
235
|
+
})
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
// ✅ GOOD: Custom limit cho specific routes
|
|
239
|
+
const uploadRouter = express.Router();
|
|
240
|
+
uploadRouter.use(express.json({ limit: "5mb" }));
|
|
241
|
+
|
|
242
|
+
uploadRouter.post("/images", upload.single("image"), (req, res) => {
|
|
243
|
+
// Handle upload
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
### 4. Spring Boot configuration
|
|
250
|
+
|
|
251
|
+
```properties
|
|
252
|
+
# ✅ GOOD: application.properties
|
|
253
|
+
spring.servlet.multipart.max-file-size=10MB
|
|
254
|
+
spring.servlet.multipart.max-request-size=20MB
|
|
255
|
+
spring.servlet.multipart.enabled=true
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
```yaml
|
|
259
|
+
# ✅ GOOD: application.yml
|
|
260
|
+
spring:
|
|
261
|
+
servlet:
|
|
262
|
+
multipart:
|
|
263
|
+
max-file-size: 10MB
|
|
264
|
+
max-request-size: 20MB
|
|
265
|
+
enabled: true
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 📊 Ngưỡng khuyến nghị
|
|
271
|
+
|
|
272
|
+
| Loại | Khuyến nghị | Medium Risk | High Risk |
|
|
273
|
+
| ---------------- | ----------- | ----------- | ---------- |
|
|
274
|
+
| **File size** | ≤ 10MB | > 20MB | > 50MB |
|
|
275
|
+
| **File count** | ≤ 10 files | > 20 files | > 50 files |
|
|
276
|
+
| **Request size** | ≤ 20MB | > 40MB | > 100MB |
|
|
277
|
+
|
|
278
|
+
### Giải thích:
|
|
279
|
+
|
|
280
|
+
- **10MB per file**: Đủ cho hầu hết use cases (images, documents, PDFs)
|
|
281
|
+
- **10 files**: Ngăn bulk upload abuse
|
|
282
|
+
- **20MB total request**: Cho phép multiple files nhưng không quá lớn
|
|
283
|
+
|
|
284
|
+
### Các trường hợp đặc biệt:
|
|
285
|
+
|
|
286
|
+
```javascript
|
|
287
|
+
// Video uploads - có thể cao hơn nhưng cần validation
|
|
288
|
+
const videoUpload = multer({
|
|
289
|
+
limits: {
|
|
290
|
+
fileSize: 50 * 1024 * 1024, // 50MB for video
|
|
291
|
+
files: 1, // Only 1 video
|
|
292
|
+
},
|
|
293
|
+
fileFilter: (req, file, cb) => {
|
|
294
|
+
if (file.mimetype.startsWith("video/")) {
|
|
295
|
+
cb(null, true);
|
|
296
|
+
} else {
|
|
297
|
+
cb(new Error("Only video files allowed"), false);
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Archive files - cần kiểm tra content
|
|
303
|
+
const archiveUpload = multer({
|
|
304
|
+
limits: {
|
|
305
|
+
fileSize: 20 * 1024 * 1024, // 20MB for zip/tar
|
|
306
|
+
},
|
|
307
|
+
fileFilter: (req, file, cb) => {
|
|
308
|
+
const allowedTypes = ["application/zip", "application/x-tar"];
|
|
309
|
+
if (allowedTypes.includes(file.mimetype)) {
|
|
310
|
+
cb(null, true);
|
|
311
|
+
} else {
|
|
312
|
+
cb(new Error("Only archive files allowed"), false);
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## 🔍 Chi tiết kỹ thuật
|
|
321
|
+
|
|
322
|
+
### Pattern detection:
|
|
323
|
+
|
|
324
|
+
1. **Multer initialization:**
|
|
325
|
+
|
|
326
|
+
```javascript
|
|
327
|
+
multer({ limits: { fileSize, files } });
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
2. **FileInterceptor decorator:**
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
@UseInterceptors(FileInterceptor('field', { limits }))
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
3. **Express middleware:**
|
|
337
|
+
```javascript
|
|
338
|
+
express.json({ limit: "10mb" });
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### AST nodes checked:
|
|
342
|
+
|
|
343
|
+
- `SyntaxKind.CallExpression` - multer(), express.json()
|
|
344
|
+
- `SyntaxKind.Decorator` - @UseInterceptors()
|
|
345
|
+
- Object literals - configuration objects
|
|
346
|
+
|
|
347
|
+
### Deduplication:
|
|
348
|
+
|
|
349
|
+
Rule sử dụng `reportedLines` Set để tránh báo duplicate errors trên cùng một dòng.
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## 🎯 Best Practices
|
|
354
|
+
|
|
355
|
+
### 1. Layered defense
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
// ✅ Multiple layers of protection
|
|
359
|
+
@Post('/upload')
|
|
360
|
+
@UseGuards(AuthGuard) // 1. Authentication
|
|
361
|
+
@UseInterceptors(FileInterceptor('file', {
|
|
362
|
+
limits: {
|
|
363
|
+
fileSize: 10 * 1024 * 1024 // 2. Size limit
|
|
364
|
+
},
|
|
365
|
+
fileFilter: (req, file, cb) => {
|
|
366
|
+
// 3. Type validation
|
|
367
|
+
const allowedTypes = ['image/jpeg', 'image/png'];
|
|
368
|
+
if (!allowedTypes.includes(file.mimetype)) {
|
|
369
|
+
return cb(new Error('Invalid file type'), false);
|
|
370
|
+
}
|
|
371
|
+
cb(null, true);
|
|
372
|
+
}
|
|
373
|
+
}))
|
|
374
|
+
async uploadFile(
|
|
375
|
+
@UploadedFile() file: Express.Multer.File,
|
|
376
|
+
@Req() req
|
|
377
|
+
) {
|
|
378
|
+
// 4. Additional server-side validation
|
|
379
|
+
if (!file) {
|
|
380
|
+
throw new BadRequestException('No file uploaded');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// 5. Magic number validation
|
|
384
|
+
const isValid = await this.fileService.validateFileContent(file);
|
|
385
|
+
if (!isValid) {
|
|
386
|
+
await this.fileService.deleteFile(file.path);
|
|
387
|
+
throw new BadRequestException('Invalid file content');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// 6. User quota check
|
|
391
|
+
const userStorage = await this.userService.getStorageUsage(req.user.id);
|
|
392
|
+
if (userStorage + file.size > USER_STORAGE_QUOTA) {
|
|
393
|
+
await this.fileService.deleteFile(file.path);
|
|
394
|
+
throw new BadRequestException('Storage quota exceeded');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return this.fileService.save(file, req.user.id);
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
### 2. Logging and monitoring
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
// ✅ Log upload attempts and rejections
|
|
407
|
+
@Post('/upload')
|
|
408
|
+
@UseInterceptors(FileInterceptor('file', {
|
|
409
|
+
limits: { fileSize: 10 * 1024 * 1024 }
|
|
410
|
+
}))
|
|
411
|
+
async uploadFile(@UploadedFile() file, @Req() req) {
|
|
412
|
+
try {
|
|
413
|
+
this.logger.log({
|
|
414
|
+
event: 'file_upload_attempt',
|
|
415
|
+
userId: req.user.id,
|
|
416
|
+
filename: file.originalname,
|
|
417
|
+
size: file.size,
|
|
418
|
+
mimetype: file.mimetype,
|
|
419
|
+
ip: req.ip
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const result = await this.fileService.save(file);
|
|
423
|
+
|
|
424
|
+
this.logger.log({
|
|
425
|
+
event: 'file_upload_success',
|
|
426
|
+
userId: req.user.id,
|
|
427
|
+
fileId: result.id
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
return result;
|
|
431
|
+
} catch (error) {
|
|
432
|
+
this.logger.error({
|
|
433
|
+
event: 'file_upload_failed',
|
|
434
|
+
userId: req.user.id,
|
|
435
|
+
filename: file?.originalname,
|
|
436
|
+
size: file?.size,
|
|
437
|
+
error: error.message,
|
|
438
|
+
ip: req.ip
|
|
439
|
+
});
|
|
440
|
+
throw error;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
### 3. Rate limiting
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
// ✅ Combine with rate limiting
|
|
451
|
+
import { Throttle } from "@nestjs/throttler";
|
|
452
|
+
|
|
453
|
+
@Controller("upload")
|
|
454
|
+
export class UploadController {
|
|
455
|
+
@Post("/avatar")
|
|
456
|
+
@Throttle(5, 60) // Max 5 uploads per 60 seconds
|
|
457
|
+
@UseInterceptors(
|
|
458
|
+
FileInterceptor("file", {
|
|
459
|
+
limits: { fileSize: 5 * 1024 * 1024 },
|
|
460
|
+
})
|
|
461
|
+
)
|
|
462
|
+
uploadAvatar(@UploadedFile() file) {
|
|
463
|
+
return this.userService.updateAvatar(file);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## 🔗 Liên quan
|
|
471
|
+
|
|
472
|
+
### Rules liên quan:
|
|
473
|
+
|
|
474
|
+
- **S025** - Server-side Validation: Validate file type, content
|
|
475
|
+
- **S055** - Content-Type Validation: Validate Content-Type header
|
|
476
|
+
|
|
477
|
+
### OWASP Top 10:
|
|
478
|
+
|
|
479
|
+
- **A04:2021** - Insecure Design
|
|
480
|
+
- **A05:2021** - Security Misconfiguration
|
|
481
|
+
|
|
482
|
+
### CWE:
|
|
483
|
+
|
|
484
|
+
- **CWE-400** - Uncontrolled Resource Consumption
|
|
485
|
+
- **CWE-770** - Allocation of Resources Without Limits or Throttling
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
## 📚 Tài liệu tham khảo
|
|
490
|
+
|
|
491
|
+
1. **OWASP File Upload Cheat Sheet**
|
|
492
|
+
https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html
|
|
493
|
+
|
|
494
|
+
2. **Multer Documentation**
|
|
495
|
+
https://github.com/expressjs/multer
|
|
496
|
+
|
|
497
|
+
3. **NestJS File Upload**
|
|
498
|
+
https://docs.nestjs.com/techniques/file-upload
|
|
499
|
+
|
|
500
|
+
4. **CWE-400: Uncontrolled Resource Consumption**
|
|
501
|
+
https://cwe.mitre.org/data/definitions/400.html
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
505
|
+
## 🚀 Testing
|
|
506
|
+
|
|
507
|
+
### Test với SunLint:
|
|
508
|
+
|
|
509
|
+
```bash
|
|
510
|
+
# Test rule trên một file
|
|
511
|
+
node cli.js --rule=S028 --input=src/upload/upload.controller.ts --engine=heuristic
|
|
512
|
+
|
|
513
|
+
# Test trên toàn bộ project
|
|
514
|
+
node cli.js --rule=S028 --input=src --engine=heuristic
|
|
515
|
+
|
|
516
|
+
# Test với verbose output
|
|
517
|
+
SUNLINT_DEBUG=1 node cli.js --rule=S028 --input=src --engine=heuristic
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
### Expected violations:
|
|
521
|
+
|
|
522
|
+
- ❌ Multer thiếu limits
|
|
523
|
+
- ❌ FileInterceptor thiếu limits
|
|
524
|
+
- ⚠️ Express middleware thiếu limit
|
|
525
|
+
- ❌ File size > 50MB (high risk)
|
|
526
|
+
- ⚠️ File size > 20MB (medium risk)
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
## ✨ Version History
|
|
531
|
+
|
|
532
|
+
- **v1.0** (2025-01-23): Initial release
|
|
533
|
+
- Support Node.js/NestJS file upload detection
|
|
534
|
+
- Multer configuration checking
|
|
535
|
+
- FileInterceptor validation
|
|
536
|
+
- Express middleware checking
|
|
537
|
+
- Threshold validation (10MB/20MB/50MB)
|