금융권 Code Injection 공격 실전 시나리오

WEB-SER-018: SSI Injection | 위험도: 5 (매우 높음) | eval/exec 함수 사용 금지

🔴 공격자 (해커)

코드 삽입 공격 준비 중...

시나리오를 선택하세요

🔵 XX은행 서비스

이자계산기 서비스 운영 중...

이자계산기 대기 중...

공격 진행 단계

시나리오를 시작하세요...

실전 공격 시나리오

취약한 서버 코드

문제 원인
# Python Flask
@app.route('/calculate', methods=['POST'])
def calculate():
    formula = request.form['formula']
    
    # ❌ 문제점: 사용자 입력을 직접 실행!
    # eval()은 모든 Python 코드 실행 가능
    result = eval(formula)
    
    return jsonify({'result': result})

# Java JSP
<%
String formula = request.getParameter("formula");

// ❌ ScriptEngine으로 코드 실행
ScriptEngineManager manager = 
    new ScriptEngineManager();
ScriptEngine engine = 
    manager.getEngineByName("JavaScript");

// 사용자 입력을 그대로 실행!
Object result = engine.eval(formula);
%>

🟢 서버 내부

XX은행 웹 애플리케이션 서버

[SERVER] Waiting for request...
서버 상태: IDLE

안전한 서버 코드

해결책
# Python Flask - 안전한 계산기
import ast
import operator

# ✅ 허용된 연산자만 정의
ALLOWED_OPERATORS = {
    ast.Add: operator.add,
    ast.Sub: operator.sub,
    ast.Mult: operator.mul,
    ast.Div: operator.truediv,
    ast.Pow: operator.pow,
    ast.USub: operator.neg,
}

def safe_eval(expression):
    # ✅ 1단계: 문자열을 AST로 파싱
    try:
        tree = ast.parse(expression, mode='eval')
    except SyntaxError:
        raise ValueError("잘못된 수식입니다")
    
    # ✅ 2단계: AST 노드 검증
    def eval_node(node):
        if isinstance(node, ast.Expression):
            return eval_node(node.body)
        
        elif isinstance(node, ast.Constant):
            # 숫자만 허용
            if isinstance(node.value, (int, float)):
                return node.value
            raise ValueError("숫자만 허용됩니다")
        
        elif isinstance(node, ast.BinOp):
            # 허용된 이항 연산만
            if type(node.op) not in ALLOWED_OPERATORS:
                raise ValueError("허용되지 않은 연산")
            
            left = eval_node(node.left)
            right = eval_node(node.right)
            op = ALLOWED_OPERATORS[type(node.op)]
            return op(left, right)
        
        elif isinstance(node, ast.UnaryOp):
            # 단항 연산 (음수)
            if type(node.op) not in ALLOWED_OPERATORS:
                raise ValueError("허용되지 않은 연산")
            
            operand = eval_node(node.operand)
            op = ALLOWED_OPERATORS[type(node.op)]
            return op(operand)
        
        else:
            raise ValueError(
                "허용되지 않은 표현식: " + 
                type(node).__name__
            )
    
    return eval_node(tree)

@app.route('/calculate', methods=['POST'])
def calculate():
    formula = request.form['formula']
    
    try:
        # ✅ 안전한 평가 함수 사용
        result = safe_eval(formula)
        
        # ✅ 로깅
        logger.info(f"Calculated: {formula} = {result}")
        
        return jsonify({'result': result})
    
    except ValueError as e:
        # ✅ 공격 시도 감지 및 로깅
        logger.warning(
            f"Blocked eval attempt: {formula}, " +
            f"ip: {request.remote_addr}"
        )
        return jsonify({'error': str(e)}), 400

# Java Spring - 안전한 계산기
import org.springframework.expression.Expression;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

@PostMapping("/calculate")
public ResponseEntity calculate(
    @RequestParam String formula) {
    
    // ✅ 1단계: 입력값 검증
    if (!isValidFormula(formula)) {
        logger.warn("Invalid formula: " + formula);
        return ResponseEntity
            .badRequest()
            .body("잘못된 수식입니다");
    }
    
    try {
        // ✅ 2단계: 제한된 파서 설정
        SpelParserConfiguration config = 
            new SpelParserConfiguration(
                false,  // autoGrowNullReferences
                false   // autoGrowCollections
            );
        
        SpelExpressionParser parser = 
            new SpelExpressionParser(config);
        
        // ✅ 3단계: 제한된 컨텍스트
        StandardEvaluationContext context = 
            new StandardEvaluationContext();
        
        // 함수/메서드 호출 차단
        context.setTypeLocator(null);
        
        Expression expression = 
            parser.parseExpression(formula);
        
        // ✅ 4단계: 안전하게 평가
        Object result = expression.getValue(
            context, 
            Double.class
        );
        
        // ✅ 5단계: 로깅
        auditLogger.info(
            "Calculated: " + formula + 
            " = " + result
        );
        
        return ResponseEntity.ok(result);
        
    } catch (Exception e) {
        // ✅ 공격 시도 차단
        logger.warn(
            "Blocked code injection: " + 
            formula + ", error: " + 
            e.getMessage()
        );
        return ResponseEntity
            .badRequest()
            .body("수식 평가 실패");
    }
}

private boolean isValidFormula(String formula) {
    // 숫자와 연산자만 허용
    return formula.matches(
        "^[0-9+\\-*/().\\s]+$"
    );
}

// 추가 방어: 정규식 화이트리스트
import java.util.regex.Pattern;

public class FormulaValidator {
    
    // 허용된 패턴만
    private static final Pattern SAFE_PATTERN = 
        Pattern.compile("^[0-9+\\-*/().\\s]+$");
    
    public static boolean isValid(String input) {
        if (input == null || input.isEmpty()) {
            return false;
        }
        
        // 위험한 키워드 차단
        String[] dangerousKeywords = {
            "import", "exec", "eval", 
            "system", "Runtime", "Process",
            "__", "class", "method"
        };
        
        String lower = input.toLowerCase();
        for (String keyword : dangerousKeywords) {
            if (lower.contains(keyword)) {
                return false;
            }
        }
        
        return SAFE_PATTERN.matcher(input).matches();
    }
}