베타 Shoulder는 베타 버전입니다 — 결과가 가끔 잘못될 수 있습니다. 여러분의 피드백이 다음에 무엇을 고칠지 결정합니다. 피드백 공유
📂

Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

🛡️ 6 개의 규칙이 이를 탐지합니다

Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.

Many file operations are intended to take place within a restricted directory. By using special elements such as '..' and '/' separators, attackers can escape outside of the restricted location to access files or directories that are elsewhere on the system.

보급률
높음
자주 악용됨
영향
치명적
1개의 치명적 심각도 규칙
예방
문서화됨
6개의 수정 예시
2 예방
2 예방

이 취약점을 수정하는 방법

6개의 Shoulder 탐지 규칙을 기반으로 한 Path Traversal 예방 전략.

Path Traversal via File Operations HIGH

Resolve the full path and verify it stays within the intended base directory

+19 -6 go
  package main
  
  import (
      "net/http"
      "os"
- )
- 
- func handler(w http.ResponseWriter, r *http.Request) {
-     filename := r.URL.Query().Get("file")
-     // Vulnerable: user input directly in file path
-     data, err := os.ReadFile("/uploads/" + filename)
+     "path/filepath"
+     "strings"
+ )
+ 
+ const baseDir = "/uploads"
+ 
+ func handler(w http.ResponseWriter, r *http.Request) {
+     filename := r.URL.Query().Get("file")
+     // Clean and resolve the path
+     cleanName := filepath.Clean(filename)
+     fullPath := filepath.Join(baseDir, cleanName)
+     absPath, _ := filepath.Abs(fullPath)
+     absBase, _ := filepath.Abs(baseDir)
+     // Verify path is within base directory
+     if !strings.HasPrefix(absPath, absBase+string(os.PathSeparator)) {
+         http.Error(w, "Forbidden", 403)
+         return
+     }
+     data, err := os.ReadFile(absPath)
      if err != nil {
          http.Error(w, "Not found", 404)
          return
      }
      w.Write(data)
  }
  
Zip Slip / Path Traversal in Archive HIGH

Validate that extracted archive paths resolve within the target directory

+22 -10 go
  package main
  
  import (
      "archive/zip"
-     "io"
-     "os"
- )
- 
- func extractZip(zipPath, destDir string) error {
-     r, _ := zip.OpenReader(zipPath)
-     defer r.Close()
-     for _, f := range r.File {
-         // Vulnerable: using archive filename directly
-         outFile, _ := os.Create(f.Name)
+     "errors"
+     "io"
+     "os"
+     "path/filepath"
+     "strings"
+ )
+ 
+ func extractZip(zipPath, destDir string) error {
+     r, _ := zip.OpenReader(zipPath)
+     defer r.Close()
+     destDir = filepath.Clean(destDir) + string(os.PathSeparator)
+     for _, f := range r.File {
+         // Safe: validate path is within destination
+         destPath := filepath.Join(destDir, filepath.Clean(f.Name))
+         if !strings.HasPrefix(destPath, destDir) {
+             return errors.New("illegal file path in archive: " + f.Name)
+         }
+         if f.FileInfo().IsDir() {
+             os.MkdirAll(destPath, 0755)
+             continue
+         }
+         outFile, _ := os.Create(destPath)
          rc, _ := f.Open()
          io.Copy(outFile, rc)
          rc.Close()
          outFile.Close()
      }
      return nil
  }
  
Path Traversal in File Operations CRITICAL

Use path.basename() to strip directory components or validate resolved paths stay within allowed directories

+2 -1 javascript
  const express = require('express');
  const fs = require('fs');
  const path = require('path');
  const app = express();
  
  app.get('/files/:filename', (req, res) => {
-   const filePath = path.join(__dirname, 'uploads', req.params.filename);
+   const safeName = path.basename(req.params.filename);
+   const filePath = path.join(__dirname, 'uploads', safeName);
    fs.readFile(filePath, (err, data) => {
      if (err) return res.status(404).send('Not found');
      res.send(data);
    });
  });
  
Zip Slip Path Traversal HIGH

Validate that extracted archive entry paths resolve within the target directory before writing

+15 -5 javascript
  const express = require('express');
  const AdmZip = require('adm-zip');
  const path = require('path');
  const app = express();
  
- app.post('/upload', (req, res) => {
-   const zip = new AdmZip(req.file.path);
-   const entries = zip.getEntries();
-   for (const entry of entries) {
-     zip.extractEntryTo(entry, './uploads/', false, true);
+ function isPathSafe(baseDir, targetPath) {
+   const resolvedBase = path.resolve(baseDir);
+   const resolvedTarget = path.resolve(baseDir, targetPath);
+   return resolvedTarget.startsWith(resolvedBase + path.sep);
+ }
+ 
+ app.post('/upload', (req, res) => {
+   const targetDir = './uploads/';
+   const zip = new AdmZip(req.file.path);
+   const entries = zip.getEntries();
+   for (const entry of entries) {
+     if (!isPathSafe(targetDir, entry.entryName)) {
+       return res.status(400).json({ error: 'Path traversal detected' });
+     }
+     zip.extractEntryTo(entry, targetDir, false, true);
    }
    res.json({ success: true });
  });
  
Path Traversal / Directory Traversal HIGH

Use os.path.basename() or pathlib to restrict file access to intended directories

+13 -6 python
- from flask import request
- 
- @app.route('/read')
- def read_file():
-     filename = request.args.get('file')
-     with open(f'/var/uploads/{filename}', 'r') as f:
+ import os
+ from flask import request, abort
+ 
+ UPLOAD_DIR = '/var/uploads'
+ 
+ @app.route('/read')
+ def read_file():
+     filename = request.args.get('file', '')
+     safe_name = os.path.basename(filename)
+     filepath = os.path.join(UPLOAD_DIR, safe_name)
+     if not os.path.isfile(filepath):
+         abort(404)
+     with open(filepath, 'r') as f:
          return f.read()
  
Zip Slip / Archive Path Traversal HIGH

Validate extracted file paths stay within the target directory before writing

+9 -4 python
  import zipfile
- 
- def extract_archive(zip_path, dest):
-     with zipfile.ZipFile(zip_path, 'r') as zf:
-         zf.extractall(dest)
+ import os
+ 
+ def safe_extract(zip_path, dest):
+     with zipfile.ZipFile(zip_path, 'r') as zf:
+         for member in zf.namelist():
+             target = os.path.normpath(os.path.join(dest, member))
+             if not target.startswith(os.path.abspath(dest)):
+                 raise ValueError(f"Path traversal: {member}")
+             zf.extract(member, dest)
  
3 탐지
3 탐지

코드에서 취약점 찾기

Shoulder를 사용하여 코드에서 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') 패턴을 스캔하세요. 6 규칙.

터미널
# Scan with Shoulder CLI
npx @shoulderdev/cli trust --cwe=22

# Or scan entire project
npx @shoulderdev/cli trust .

탐지 규칙 (6)

4 경고 신호
4 경고 신호

코드 리뷰에서 주의할 점

이 패턴은 잠재적인 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') 취약점을 나타냅니다. 코드 리뷰와 보안 감사 중에 찾아보세요.

🟠
unsafe extraction of zip/tar archives without path validation, which can lead to arbitrary file writ javascript-zip-slip
🟠
untrusted user input being used in file system operations without proper validation python-path-traversal
🟠
unsafe extraction of ZIP/TAR archives without path validation python-zip-slip
🔴
untrusted user input used in file system operations without proper validation javascript-path-traversal
🔍

코드베이스를 스캔하세요: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

Shoulder CLI는 전체 코드베이스에서 취약한 패턴을 찾아냅니다.