Skip to main content
Glama
injection.ts7.11 kB
/** * SQL Injection 취약점 스캐너 * * 사용자 입력이 SQL 쿼리에 직접 들어가는 위험한 패턴을 찾습니다. * 이거 진짜 기본 중의 기본인데, AI가 생성한 코드에서 의외로 많이 나와요. * Prepared Statement 쓰면 되는데 왜 안 쓰는지 모르겠음... * * @author zerry */ import { SecurityIssue } from '../types.js'; interface InjectionPattern { name: string; pattern: RegExp; message: string; fix: string; languages: string[]; } /** * SQL Injection 패턴 정의 * * 언어별로 위험한 패턴이 조금씩 달라서 분리해뒀어요. */ const INJECTION_PATTERNS: InjectionPattern[] = [ // JavaScript/TypeScript - 문자열 조합 쿼리 { name: 'String Concatenation SQL', pattern: /(?:query|execute|sql)\s*\(\s*['"`](?:SELECT|INSERT|UPDATE|DELETE).*\+.*(?:req\.|params\.|body\.|query\.)/gi, message: '문자열 연결로 SQL 쿼리를 만들고 있습니다. SQL Injection에 취약합니다.', fix: 'Prepared Statement나 Parameterized Query를 사용하세요. 예: db.query("SELECT * FROM users WHERE id = ?", [userId])', languages: ['javascript', 'typescript'], }, { name: 'Template Literal SQL', pattern: /(?:query|execute|sql)\s*\(\s*`(?:SELECT|INSERT|UPDATE|DELETE)[^`]*\$\{[^}]+\}/gi, message: '템플릿 리터럴로 SQL 쿼리에 변수를 삽입하고 있습니다. SQL Injection에 취약합니다.', fix: 'Prepared Statement를 사용하세요. 템플릿 리터럴 안에 직접 변수를 넣지 마세요.', languages: ['javascript', 'typescript'], }, // Python - f-string이나 format으로 쿼리 만들기 { name: 'Python f-string SQL', pattern: /(?:execute|cursor\.execute)\s*\(\s*f['"](?:SELECT|INSERT|UPDATE|DELETE)[^'"]*\{[^}]+\}/gi, message: 'f-string으로 SQL 쿼리를 만들고 있습니다. SQL Injection에 취약합니다.', fix: 'execute(sql, params) 형태로 파라미터를 분리하세요. 예: cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))', languages: ['python'], }, { name: 'Python format SQL', pattern: /(?:execute|cursor\.execute)\s*\(\s*['"](?:SELECT|INSERT|UPDATE|DELETE)[^'"]*['"]\.format\s*\(/gi, message: '.format()으로 SQL 쿼리를 만들고 있습니다. SQL Injection에 취약합니다.', fix: 'Parameterized Query를 사용하세요.', languages: ['python'], }, { name: 'Python % formatting SQL', pattern: /(?:execute|cursor\.execute)\s*\(\s*['"](?:SELECT|INSERT|UPDATE|DELETE)[^'"]*%s[^'"]*['"].*%/gi, message: '% 포맷팅으로 SQL 쿼리를 만들고 있습니다. SQL Injection에 취약합니다.', fix: 'execute()의 두 번째 인자로 파라미터를 전달하세요.', languages: ['python'], }, // Java - 문자열 연결 { name: 'Java String Concat SQL', pattern: /(?:executeQuery|executeUpdate|prepareStatement)\s*\(\s*['"](?:SELECT|INSERT|UPDATE|DELETE)[^'"]*['"]\s*\+/gi, message: '문자열 연결로 SQL 쿼리를 만들고 있습니다.', fix: 'PreparedStatement를 사용하세요. 예: PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?"); ps.setInt(1, userId);', languages: ['java'], }, // 공통 - 위험한 패턴들 { name: 'Raw SQL with Variable', pattern: /['"`](?:SELECT|INSERT|UPDATE|DELETE)\s+.+(?:WHERE|VALUES|SET)\s+.+['"`]\s*\+\s*\w+/gi, message: 'SQL 쿼리에 변수를 직접 연결하고 있습니다.', fix: 'ORM을 사용하거나 Prepared Statement를 사용하세요.', languages: ['javascript', 'typescript', 'python', 'java', 'go'], }, // NoSQL Injection (MongoDB 등) { name: 'MongoDB Injection', pattern: /(?:find|findOne|updateOne|deleteOne)\s*\(\s*\{[^}]*:\s*(?:req\.|params\.|body\.|query\.)/gi, message: '사용자 입력이 MongoDB 쿼리에 직접 들어가고 있습니다. NoSQL Injection에 취약합니다.', fix: 'mongo-sanitize 같은 라이브러리로 입력을 sanitize하거나, 스키마 검증을 추가하세요.', languages: ['javascript', 'typescript'], }, // Command Injection { name: 'Command Injection', pattern: /(?:exec|spawn|execSync|spawnSync|system|popen)\s*\([^)]*(?:req\.|params\.|body\.|query\.|input)/gi, message: '사용자 입력이 시스템 명령어에 들어가고 있습니다. Command Injection에 취약합니다!', fix: '사용자 입력을 시스템 명령어에 사용하지 마세요. 꼭 필요하다면 화이트리스트 검증을 하세요.', languages: ['javascript', 'typescript', 'python'], }, ]; /** * SQL Injection 취약점을 검사합니다. * * 언어별로 다른 패턴을 적용하고, * 위험한 쿼리 구성 방식을 찾아냅니다. */ export function scanInjection(code: string, language: string): SecurityIssue[] { const issues: SecurityIssue[] = []; const lines = code.split('\n'); // 해당 언어에 적용되는 패턴만 필터링 const applicablePatterns = INJECTION_PATTERNS.filter( p => p.languages.includes(language) || p.languages.includes('*') ); for (const pattern of applicablePatterns) { // 패턴 리셋 (global flag 때문에 필요) pattern.pattern.lastIndex = 0; const matches = code.matchAll(pattern.pattern); for (const match of matches) { // 라인 번호 찾기 const lineNumber = findLineNumber(code, match.index || 0); const line = lines[lineNumber - 1] || ''; // 주석은 스킵 if (isComment(line, language)) { continue; } issues.push({ type: pattern.name, severity: pattern.name.includes('Command') ? 'critical' : 'high', message: pattern.message, fix: pattern.fix, line: lineNumber, match: match[0].slice(0, 100), // 너무 길면 잘라내기 owaspCategory: 'A03:2021 – Injection', cweId: 'CWE-89', }); } } return issues; } /** * 문자 인덱스로 라인 번호 찾기 */ function findLineNumber(code: string, index: number): number { const beforeMatch = code.slice(0, index); return (beforeMatch.match(/\n/g) || []).length + 1; } /** * 주석인지 체크 (언어별로 다름) */ function isComment(line: string, language: string): boolean { const trimmed = line.trim(); switch (language) { case 'python': return trimmed.startsWith('#'); case 'java': case 'javascript': case 'typescript': case 'go': return trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*'); default: return trimmed.startsWith('//') || trimmed.startsWith('#'); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ongjin/security-scanner-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server