템플릿 객체 주입

Template Object Injection

설명

템플릿 객체 주입은 사용자가 제어하는 객체(req.body, req.query 등)를 템플릿 엔진의 locals/context 인자로 그대로 전달할 때 발생합니다. Node.js 템플릿 엔진과 어댑터는 종류·버전에 따라 locals 안의 특수 키(layout, filename, settings, partials, helpers 등)를 템플릿 동작이나 파일 경로에 영향을 주는 옵션으로 해석할 수 있습니다. 공격자는 이러한 특수 키를 요청에 삽입하여 템플릿의 레이아웃/인클루드 파일 경로를 바꾸거나 내부 옵션을 조작함으로써, 로컬 파일 읽기(LFR) 또는 원격 코드 실행(RCE)까지 유발할 수 있습니다. 예: {"layout":"../../../../etc/passwd"}, {"filename":"/etc/passwd"}, {"settings":{"views":"/"}} 등을 주입해 민감 파일을 노출시키거나, 엔진/런타임 옵션을 변경해 코드가 평가되도록 만들 수 있습니다.

잠재적 영향

  • 로컬 파일 노출(LFR): layout/filename/settings 등의 키를 주입해 임의 경로의 파일을 템플릿으로 읽어 민감 정보가 유출될 수 있음.
  • 원격 코드 실행(RCE): 일부 엔진/옵션 조합에서 사용자 입력이 코드로 평가되도록 유도하여 서버 측 임의 코드 실행이 가능할 수 있음.
  • 템플릿/뷰 조작: 의도하지 않은 레이아웃, partials, helpers 주입으로 화면 변조, 보안 검증 우회, 비즈니스 로직 혼선을 유발.
  • 정보 노출 및 권한 상승: 설정 객체(settings, runtimeOptions 등) 변경으로 디버그 정보 노출, 경로 탐색, 추가 취약점 연계 공격 가능.

해결 방법

  • 허용 목록 기반 전달: 템플릿에 필요한 키만 명시적으로 선택해 새 객체에 담아 전달하세요(예: { name, bio } 만). 전체 req.body/req.query를 넘기거나 스프레드({...req.body})를 사용하지 마세요.
  • 특수/예약 키 차단: layout, filename, views, partials, settings, helpers, runtimeOptions 등 템플릿 제어 키는 사용자 입력에서 제거하거나 덮어쓰지 못하게 하세요.
  • 스키마 검증: Joi/Zod 등으로 전달할 locals의 스키마를 정의해 타입/값 범위를 검증하세요. 알 수 없는 키는 거부하세요.
  • 템플릿 엔진 보안 설정: 가능한 경우 코드 평가 기능 비활성화, 안전 모드 사용, include 경로 제한(고정 디렉터리) 등을 적용하세요.

예시

profile 템플릿과 뷰 엔진 설치·설정의 나머지 부분은 생략했습니다.

변경 전

javascript
const express = require("express");
const app = express();
app.set("view engine", "hbs");
app.use(express.json());

// 변경 전: 사용자 객체를 그대로 템플릿 locals로 전달
app.post("/profile", (req, res) => {
  // 공격자는 { "layout": "../../../../etc/passwd" } 와 같이 특수 키를 주입 가능
  res.render("profile", req.body);
});

변경 후

javascript
const express = require("express");
const app = express();
app.set("view engine", "hbs");
app.use(express.json());

// 허용 키만 선택해서 새 객체를 만들어 전달
const SAFE_KEYS = ["name", "bio", "location"];
function pick(obj, keys) {
  const out = {};
  for (const k of keys) {
    if (typeof obj[k] === "string") out[k] = obj[k];
  }
  return out;
}

app.post("/profile", (req, res) => {
  const locals = pick(req.body || {}, SAFE_KEYS);
  res.render("profile", locals);
});

설명:

  • 변경 전: req.body 전체를 render의 두 번째 인자로 넘겨 템플릿 엔진이 해석하는 특수 키(layout, filename, settings 등)까지 공격자가 주입할 수 있습니다. 이로 인해 파일 경로 조작을 통한 LFR 또는 엔진 옵션 조작을 통한 RCE로 악용될 수 있습니다.
  • 변경 후: 템플릿에서 실제로 필요한 키만 허용 목록으로 선별하여 새 객체에 담아 전달합니다. 이 경로에서는 사용자가 템플릿 제어 키를 전달하지 못합니다. 필드별 길이·값 검증과 템플릿 자체의 안전성은 별도로 확인하세요. Express가 렌더링 옵션을 추가할 수 있도록 전달 객체를 동결하지 않습니다.

참조