[P0][security] frontend SSR JSON 삽입 경로 XSS 방어 escaping 적용 #22

Closed
opened 2026-05-02 04:34:04 +00:00 by boxqkrtm · 1 comment

문제

crates/portal-relay/src/api/frontend.rs에서 public leases를 serde_json::to_string()<script type="application/json"> 내부에 그대로 삽입합니다.

let leases = serde_json::to_string(&leases).unwrap_or_else(|_| "[]".to_string());
let ssr_script =
    format!("<script id="__SSR_DATA__" type="application/json">{leases}</script>");

serde_json::to_string()은 HTML script context escaping을 보장하지 않습니다.

위험

lease metadata 등에 </script><script>...</script> 형태의 문자열이 들어가면 script tag가 조기 종료되어 XSS가 가능합니다. landing card HTML은 escape_html()을 쓰더라도 SSR JSON 삽입 경로는 별도 방어가 필요합니다.

제안

  • JSON을 script tag에 넣기 전 HTML script context escaping 적용
  • 최소 escape 대상: <, >, &, U+2028, U+2029
  • 또는 SSR JSON을 inline script가 아닌 별도 JSON endpoint/fetch 방식으로 분리

예시:

fn escape_json_for_html_script(raw: &str) -> String {
    raw.replace('<', "\u003c")
       .replace('>', "\u003e")
       .replace('&', "\u0026")
       .replace('\u{2028}', "\u2028")
       .replace('\u{2029}', "\u2029")
}

완료 기준

  • </script>가 포함된 lease metadata가 HTML에서 script tag를 닫지 않음
  • SSR JSON 파싱은 기존처럼 동작
  • XSS regression test 추가

검토 기준: 업로드된 Rust portal-relay 코드 정적 리뷰. 리뷰 환경에서는 cargo check/test/clippy를 실행하지 못했습니다.

## 문제 `crates/portal-relay/src/api/frontend.rs`에서 public leases를 `serde_json::to_string()` 후 `<script type="application/json">` 내부에 그대로 삽입합니다. ```rust let leases = serde_json::to_string(&leases).unwrap_or_else(|_| "[]".to_string()); let ssr_script = format!("<script id="__SSR_DATA__" type="application/json">{leases}</script>"); ``` `serde_json::to_string()`은 HTML script context escaping을 보장하지 않습니다. ## 위험 lease metadata 등에 `</script><script>...</script>` 형태의 문자열이 들어가면 script tag가 조기 종료되어 XSS가 가능합니다. landing card HTML은 `escape_html()`을 쓰더라도 SSR JSON 삽입 경로는 별도 방어가 필요합니다. ## 제안 - JSON을 script tag에 넣기 전 HTML script context escaping 적용 - 최소 escape 대상: `<`, `>`, `&`, U+2028, U+2029 - 또는 SSR JSON을 inline script가 아닌 별도 JSON endpoint/fetch 방식으로 분리 예시: ```rust fn escape_json_for_html_script(raw: &str) -> String { raw.replace('<', "\u003c") .replace('>', "\u003e") .replace('&', "\u0026") .replace('\u{2028}', "\u2028") .replace('\u{2029}', "\u2029") } ``` ## 완료 기준 - `</script>`가 포함된 lease metadata가 HTML에서 script tag를 닫지 않음 - SSR JSON 파싱은 기존처럼 동작 - XSS regression test 추가 --- 검토 기준: 업로드된 Rust `portal-relay` 코드 정적 리뷰. 리뷰 환경에서는 `cargo check/test/clippy`를 실행하지 못했습니다.
Owner

수용했습니다. PR #62에서 SSR JSON을 script context용으로 <, >, &, U+2028, U+2029 escape하도록 변경하고 breakout 회귀 테스트를 추가했습니다. 검증: cargo test --locked, cargo clippy --locked --all-targets -- -D warnings.

수용했습니다. PR #62에서 SSR JSON을 script context용으로 <, >, &, U+2028, U+2029 escape하도록 변경하고 </script> breakout 회귀 테스트를 추가했습니다. 검증: cargo test --locked, cargo clippy --locked --all-targets -- -D warnings.
gofix closed this issue 2026-05-03 18:58:35 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
gofix/portal-tunnel-rs#22
No description provided.