2015년 6월 27일 토요일

JSON 문자열의 데이터를 HTML escape 할때 주의점

브라우저에서 AJAX로 JSON을 요청한 경우, 서버에서 JSON 문자열을 리턴해주는데, XSS 방지를 위해 특수문자 6개( & < > " ' / )는 HTML 엔티티로 변환해서 넘겨준다.

return input.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll("\\\\\"", "&quot;")
.replaceAll("'", "&#x27;")
.replaceAll("/", "&#x2F;");

그런데 "의 경우에는 " 자체가 JSON에서 사용하는 특수문자이기 때문에 \"와 같이 표현되도록 JSON이 생성된다. 그래서 "를 &quot;로 변경하면 실제로는 \" 가 \&quot;가 되므로 escape 문자인 \가 데이터인 것처럼 변경된다. 그래서 \"를 &quot;로 변경해야 한다.

그런데 값이 \일때는 또다른 문제가 발생한다.

name이라는 항목의 실제값이 \ 라고 가정하면,
JSON인코딩 문자열은 {"name":"\\"} 가 된다.
이게 위 XSS 치환 로직을 거치면 {"name":"\&quot;}가 되면서 JSON 규칙에서 어긋나게된다.

JSON spec에 따르면 값(value)는 쌍따옴표(")로 둘러싸여져 있고, 값 바로뒤에는 특수문자 } , ]만 나올 수 있다. 쌍따옴표(")가 값(value)이 아닌 이름(name)일 수 도 있으므로 : 도 나올 수 있다. 그래서 정규식에 negative lookahead를 적용해서 \\\"(?!,|]|:|})를 &quot;로 변경하도록 적용하니 값이 \일때도 정상적으로 처리된다. 그리고 Java String의 escape 문자도 적용되어야 하니 Java String에 넣을려면 \\\\\"(?!,|]|:|}) 가 되어야 한다.
실제 데이터가 ":" 일 때는 \":\" 처럼 데이터가 생성된다. 그래서 위에 취소선을 그은 방법대로 하면 , } ] : 앞에 나타나는 "는 치환되지 않는다. 그래서 negative lookbehind를 적용해서 짝수개의 \\\\가 발생하고 바로 나타나는 \"에 대해서 치환하도록 정규식을 변경했더니 정상적으로 치환이 된다.
특정 패턴이 0번 혹은 짝수번 나타나는지를 확인하기 위해 negative lookbehind와 짝수번 패턴의 *를 사용했다. 참고 \\가 안나오고 \\\\가 0번이상 발생하는 패턴. 만들어진 정규식은 (?<!\\)(\\\\)*
Java String의 replaceAll 사용시 짝수번패턴은 치환하지 않고 그대로 유지하기 위해 치환할 부분을 제외한 전체부분은 그룹으로 묶고 치환해서 들어갈 문자열에 $1으로 지정. 참고


return input.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll("((?<!\\\\)(\\\\\\\\)*)(\\\\\\\")", "$1&quot;")
.replaceAll("'", "&#x27;")
.replaceAll("/", "&#x2F;");

댓글 없음:

댓글 쓰기