JSP コメント・HTML コメント・式:実行段階、EL、XSS 安全性ガイド

まず server template と browser source を区別する

Standard syntax の .jsp page では、<%-- ... --%> は container が page translation 時に無視する JSP comment です。<!-- ... --> は HTML comment で response の template text なので client へ送られます。HTML comment 内の JSP expression、Expression Language(EL)、tag も server で実行され、その結果が「View Source」に現れる場合があります。

したがって HTML comment は、data を隠す、server code を無効にする、XSS を防ぐための境界ではありません。本 guide は 2026 年 9 月 1 日に最終確認し、Jakarta Pages 4.0、Servlet 6.1、Expression Language 6.0、Jakarta Tags 3.0 を基準に、scriptlet より servlet/controller、EL、JSTL を優先します。

1. JSP の translation phase と request phase

Jakarta Pages 4.0 specification は 2 phase を定義します。Container はまず JSP を servlet class に translate・compile し、request ごとにその servlet を実行します。Translation は初回 request 時、または deploy/build 時の precompile で行われます。

Page elementTranslation phaseRequest/response result
<%@ page ... %> などの JSP directivePage translation を設定Directive 自体は response に書かれない
JSP comment <%-- ... --%>Body は完全に無視Response に現れず、内部 EL・scripting element も実行されない
HTML comment <!-- ... -->Template text として処理Comment と内部の dynamic result が response に出る
EL ${...}Page implementation の一部として parseJSP/EL rule に従い request 時に評価
JSP expression <%= ... %>Java output logic へ translateRequest 時に実行して直接出力する、移行対象の scripting element
JSTL/custom tagTag invocation へ translateRequest 時に実行し、出力は tag に依存

Deploy 後に page が再 translate されるかは container、precompile、change detection に依存します。Development auto-reload を production deploy の証明にせず、WAR、container、JDK の version と digest を記録します。

2. HTML comment、JSP comment、code comment は別物

これは動作だけを示し、private data や user input は含みません。

<%-- Server-only note: the EL here is ignored: ${1 + 1} --%>
<!-- Client-visible note: the EL result is ${1 + 1} -->

Response には HTML comment だけが残り、通常は値が評価済みです。

<!-- Client-visible note: the EL result is 2 -->

Java の /// ... /、JavaScript/CSS comment、HTML comment はそれぞれの parser にだけ意味があります。JSP comment の代わりにはなりません。JSP document(.jspx)は XML syntax を使い、comment と escaping rule が異なります。この standard-syntax 例を別 test なしに JSP document へコピーしないでください。

3. 過去の問いへの回答:評価されるが、scriptlet は使わない

2011 年原文は <%= expression %> を HTML comment 内に置きました。Specification は template content の HTML comment に dynamic expression を認めるため、expression は実行され、その出力は client に見えます。しかし syntax が有効でも安全設計とは限りません。Expression は side effect、exception、internal state disclosure を起こせ、古い Date#toLocaleString() API も obsolete です。

Scriptlet を HTML comment で囲んで「無効」にしないでください。HTML comment は server execution を止めません。JSP comment は body を無視しますが、business logic を JSP に残すと test、encoding、upgrade が難しくなります。Computation、I/O、authorization decision、date formatting は controller/service へ移し、準備済み表示値だけを request scope に置きます。

4. EL、scriptlet、data boundary

Jakarta Expression Language 6.0 は immediate ${...} evaluation と deferred #{...} evaluation を区別し、hosting technology が利用時期を決めます。通常の JSP view は ${...} で scope 内の bean、map、attribute を読みます。

EL は HTML encoder ではありません。${param.name} を直接出しても自動的に safe HTML にはなりません。User に EL source、property name、method call を組み立てさせてはいけません。Expression は template author が固定し、user value は data に限定します。Resolver が公開する object/method を制限し、view で authorization decision を行わず、exception や comment で internal class、path、query、token を漏らしません。

Scriptlet <% ... %>、declaration <%! ... %>、expression <%= ... %> は Java を template に混在させます。Maintenance guide では使用しません。Migration では characterization test を先に作り、logic を servlet/controller へ移し、表示 branch と loop を EL/JSTL に置換します。

5. 安全な servlet/controller と view の境界

Controller は validation、authorization、display model の準備を行い、直接 request できない WEB-INF 配下の JSP へ forward します。Sample data は trusted constant です。実 application では authentication と authorization を混同しません。

package example;

import java.io.IOException;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@WebServlet("/welcome")
public final class WelcomeServlet extends HttpServlet {
    @Override
    protected void doGet(
            HttpServletRequest request,
            HttpServletResponse response
    ) throws ServletException, IOException {
        request.setAttribute("message", "Welcome");
        request.getRequestDispatcher("/WEB-INF/views/welcome.jsp")
                .forward(request, response);
    }
}

View は Jakarta Tags 3.0 の standard core URI を使います。c:outescapeXml default は true ですが、review のため明示します。

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Welcome</title>
</head>
<body>
  <%-- Internal implementation note; never place secrets here. --%>
  <p><c:out value="${requestScope.message}" escapeXml="true" /></p>
</body>
</html>

Application には Jakarta Tags 3.0 と compatible で version-pinned な implementation が必要です。Servlet/JSP container に必ず bundled されるわけではありません。Source、version、license を記録せず tag-library JAR を shared server directory に手作業で置かないでください。

6. Output encoding は HTML context に合わせる

OWASP XSS Prevention Cheat Sheet は正確な output context に合う encoding を要求します。Input validation は business correctness に役立ちますが output encoding の代替ではありません。CSP は defense in depth であり encoding の代わりではありません。

Output locationBaseline treatmentStop condition
HTML text nodec:out または trusted HTML text encoderTrusted HTML を含める必要がある。別途 audit した sanitizer と type boundary を導入
Quoted HTML attributeHTML attribute encoder を使い、必ず quoteEvent handler、stylesrcdoc など危険な attribute
URL parameterURL builder/encoder で parameter を構築し、最終 attribute を HTML attribute encodeUser が scheme または origin を制御
JavaScript string/valueJavaScript-context encoder または safe JSON serializerHTML encoding を JavaScript encoding として扱おうとしている
CSS valueDynamic CSS を避け、必要なら CSS-context encoder と allowlistUser が property、selector、URL を制御
HTML commentUser value、secret、diagnostic detail を置かないValue が comment delimiter を含む、または公開で情報漏えい

c:out は基本 HTML/XML escaping には適しますが、全 context 共通 encoder ではありません。OWASP Java Encoder は context-specific Java/JSP encoding API を提供します。採用時は compatible version を pin し dependency scan します。未監査 string に escapeXml="false" を使いません。

7. String 連結ではなく安全に URL を構築する

Jakarta Tags の c:urlc:param は application path と parameter に明確な境界を作り、c:out が HTML attribute を保護します。

<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<c:url var="profileUrl" value="/profile">
  <c:param name="id" value="${requestScope.publicUserId}" />
</c:url>
<a href="<c:out value="${profileUrl}" escapeXml="true" />">Profile</a>

この例は access を authorize せず、external scheme も許可しません。Controller は ID validation と object-level authorization を行います。Redirect、download、javascript: URL、open redirect、cross-origin destination には別 allowlist が必要で、URL encoding だけで trusted にはなりません。

8. Comment は secret storage や debug channel ではない

HTML comment は response、CDN、proxy、browser cache、recording、test artifact に入ります。Username、email、internal host、file path、stack trace、SQL、feature flag、脆弱 version 情報、session ID、token、authorization decision を出力しません。c:out で encode しても情報は公開されます。

JSP comment は response には出ませんが source、WAR、backup、code-review system に残るため credential を保存できません。Structured log は server-side request ID で関連付けた必要最小 field だけにします。Personal identifier は削除・hash 化し、cookie、authorization header、request-body secret を記録しません。

WHATWG HTML comment syntax は comment content も制限します。User value が delimiter を壊したり別 DOM を作ったりできます。Untrusted dynamic value を HTML comment に置かないことが最安全です。

9. 最小の実行可能な動作例

次を comment-demo/index.jsp として保存します。Built-in JSP EL のみで JSTL に依存せず、container boundary test を小さく保ちます。Production view では前述の encoding を使用します。

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
<%-- Server-only marker: this value is ignored: ${1 + 1} --%>
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>JSP comment boundary test</title>
</head>
<body>
  <!-- Public diagnostic only: EL result ${1 + 1} -->
  <p id="result">EL result: ${1 + 1}</p>
</body>
</html>

Constant arithmetic は phase boundary の観察だけに使います。Request parameter を直接出してよいという意味ではありません。EL result は HTML-context encoding を自動では受けません。

10. 制限された container test boundary

Apache Tomcat 11 documentation は Jakarta Servlet 6.1 と Pages 4.0 に対応します。以下は Docker Official Image の Tomcat pageを使い、loopback のみに bind、webapp を read-only mount、write directory を temporary filesystem に限定します。Tag を pull して immutable digest を記録し、その digest を実行します。将来の再現では記録した digest を直接使います。

set -eu

TEST_ROOT="$PWD/comment-demo"
TOMCAT_TAG=tomcat:11.0-jre21-temurin-noble

test -f "$TEST_ROOT/index.jsp"
docker pull "$TOMCAT_TAG"
TOMCAT_IMAGE=$(docker image inspect "$TOMCAT_TAG" --format '{{index .RepoDigests 0}}')
test -n "$TOMCAT_IMAGE"
echo "$TOMCAT_IMAGE"

docker run --rm --name jsp-comment-test --read-only --cap-drop=ALL --security-opt=no-new-privileges --tmpfs /usr/local/tomcat/conf/Catalina --tmpfs /usr/local/tomcat/temp --tmpfs /usr/local/tomcat/work --tmpfs /usr/local/tomcat/logs --publish 127.0.0.1:18080:8080 --volume "$TEST_ROOT:/usr/local/tomcat/webapps/ROOT:ro" "$TOMCAT_IMAGE"

Command は foreground で動きます。Ctrl-C で止めると --rm が container を削除します。これは local semantics test であり production-hardening template ではありません。Source repository、secret、Docker socket、production data を mount しないでください。

11. Response、log、failure boundary を検証する

別 terminal から local port を request します。HTML comment に評価済み 2 があり、JSP-comment marker が全くないことを要求します。

set -eu

curl --fail --silent --show-error --retry 20 --retry-all-errors --retry-delay 1 http://127.0.0.1:18080/ > response.html
grep --fixed-strings '<!-- Public diagnostic only: EL result 2 -->' response.html
grep --fixed-strings '<p id="result">EL result: 2</p>' response.html

if grep --quiet --fixed-strings 'Server-only marker' response.html; then
  exit 1
fi

Container log に JSP translation/compile error がないことも確認します。Response に ${1 + 1} が残る場合、file が JSP servlet で処理されていない、page/application config で EL が disabled、mapping が誤り、または cached static file の可能性があります。Scriptlet で回避しません。Server-only marker が現れたら、意図した artifact と container を request したとまず証明し、file syntax を調べます。

Test artifact に user data はありませんが test 後に削除します。CI では JDK、Tomcat image digest、WAR hash、first-request と later-request result を記録し、1 台の development server だけでなく supported target container ごとに繰り返します。

12. Scriptlet migration checklist

  • [ ] 既存 response body、status code、header、authorization behavior の characterization test を作る。
  • [ ] DB access、I/O、date formatting、branch、authorization decision を service/controller へ移す。
  • [ ] JSP には必要最小の request/view model だけを公開する。
  • [ ] Scriptlet の loop と branch を EL/JSTL に置換し、user input から EL を構築しない。
  • [ ] 全 dynamic output を HTML、attribute、URL、JavaScript、CSS context ごとに review する。
  • [ ] Client-side HTML comment から secret、diagnostic、user value を除く。
  • [ ] Login、error page、i18n、null、Unicode、malicious payload を test する。
  • [ ] Jakarta API、Tags implementation、JDK、container version と digest を pin する。
  • [ ] Staging で全 JSP を precompile または trigger し、translation warning/error を release blocker にする。
  • [ ] Response body、browser DOM、security scan を合わせて検証し、見た目だけで判断しない。

13. 現在の公式・権威あるリファレンス

14. 2011/2023 年原文の正確なアーカイブ

以下は source_export の可視本文全体です。原文の文字、link、空白、句読点、nonstandard comment dash は変更せず、不活性な外側 code fence だけを追加しました。元の export と Git history は変更していません。

警告:archive は scriptlet、obsolete date API、client-visible comment 内の dynamic data を使います。Historical audit のためだけに保持しています。上の maintained guide を使用してください。


在客户端显示一个注释

Table of Contents

Toggle

- [JSP 语法](https://blog.lazying.art/en/html/computer_internet/java_j2ee_jsp/472/html%e6%b3%a8%e9%87%8a%e4%b8%ad%e6%8f%92%e5%85%a5jsp%e8%a1%a8%e8%be%be%e5%bc%8f.html/#JSP_%E8%AF%AD%E6%B3%95)
- [例子 1](https://blog.lazying.art/en/html/computer_internet/java_j2ee_jsp/472/html%e6%b3%a8%e9%87%8a%e4%b8%ad%e6%8f%92%e5%85%a5jsp%e8%a1%a8%e8%be%be%e5%bc%8f.html/#%E4%BE%8B%E5%AD%90_1)
- [例子 2](https://blog.lazying.art/en/html/computer_internet/java_j2ee_jsp/472/html%e6%b3%a8%e9%87%8a%e4%b8%ad%e6%8f%92%e5%85%a5jsp%e8%a1%a8%e8%be%be%e5%bc%8f.html/#%E4%BE%8B%E5%AD%90_2)
- [总结](https://blog.lazying.art/en/html/computer_internet/java_j2ee_jsp/472/html%e6%b3%a8%e9%87%8a%e4%b8%ad%e6%8f%92%e5%85%a5jsp%e8%a1%a8%e8%be%be%e5%bc%8f.html/#%E6%80%BB%E7%BB%93)

## JSP 语法

<!– comment [ <%= expression %> ] –>

## 例子 1

<!– This file displays the user login screen –>

在客户端的HTML源代码中产生和上面一样的数据:

<!– This file displays the user login screen –>

## 例子 2

<!– This page was loaded on <%= (new java.util.Date()).toLocaleString() %> –>

在客户端的HTML源代码中显示为:

<!– This page was loaded on January 1, 2000 –>

## 总结

这种注释和HTML注释很像,也就是它可以在”查看源代码”中看到。

唯一有些不同的就是,你可以在这个注释中用表达式(例子2所示).这个表达式是不定的,由页面不同而不同,你能够使用各种表达式,只要是合法的就行,更多的请看表达式。

//和/* */在html里也是常用的注释,但只能用在js和CSS语言,不对HTML语言起作用!

Leave a Reply