WEB-SER-018: SSI Injection | 위험도: 5 (매우 높음) | eval/exec 함수 사용 금지
코드 삽입 공격 준비 중...
시나리오를 선택하세요
이자계산기 서비스 운영 중...
이자계산기 대기 중...
# 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은행 웹 애플리케이션 서버
# 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();
}
}