14 セキュリティ - Reference Documentation
Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith
Version: 2.3.0
Translated by: T.Yamamoto, Japanese Grails Doc Translating Team. Special thanks to NTT Software.
【注意】このドキュメントの内容はスナップショットバージョンを元に*意訳*されているため、一部現行バージョンでは未対応の機能もあります。
Table of Contents
14 セキュリティ
Grailsが自動的に行うこと
- All standard database access via GORM domain objects is automatically SQL escaped to prevent SQL injection attacks
- The default scaffolding templates HTML escape all data fields when displayed
- Grails link creating tags (link, form, createLink, createLinkTo and others) all use appropriate escaping mechanisms to prevent code injection
- Grails provides codecs to let you trivially escape data when rendered as HTML, JavaScript and URLs to prevent injection attacks here.
- GORM経由でのすべての標準的なデータベースアクセスにおける、SQLインジェクション攻撃を防ぐための自動的なエスケープ処理
- デフォルトのスカッフォルドテンプレートのHTMLにおける、すべてのデータフィールドに対する表示時のエスケープ処理
- Grailsのリンク作成タグ(link、form、createLink、createLinkTo他)における、コードインジェクションを防ぐための適切なエスケープ機構
- GrailsでHTML、JavaScript、URLをレンダリングする際の、データを手軽にエスケープ処理しインジェクション攻撃を防ぐコーデックの提供
14.1 攻撃からの防御
SQLインジェクション
def vulnerable() { def books = Book.find("from Book as b where b.title ='" + params.title + "'") }
def vulnerable() {
def books = Book.find("from Book as b where b.title ='${params.title}'")
}
def safe() {
def books = Book.find("from Book as b where b.title = ?",
[params.title])
}
def safe() {
def books = Book.find("from Book as b where b.title = :title",
[title: params.title])
}
フィッシング
XSS - クロスサイトスクリプティングインジェクション
successURL
parameter for example to determine where to redirect a user to after a successful login, attackers can imitate your login procedure using your own site, and then redirect the user back to their own site once logged in, potentially allowing JavaScript code to then exploit the logged-in account on the site.successURL
パラメータを使うような場合、攻撃者は、あなた自身のサイトを利用してログイン手順を偽装し、ユーザがログインしようとしたときに攻撃者自身のサイトにリダイレクトさせることができてしまいます。これは、潜在的に、サイト上のログインアカウントを悪用するJavaScriptコードを許していることになります。クロスサイトリクエストフォージェリ
useToken
attribute on your forms. See Handling Duplicate Form Submissions for more information on how to use it. An additional measure would be to not use remember-me cookies.useToken
属性を使うことです。使い方についての詳細は、フォーム二重送信のハンドリングを参照してください。加えて、「次回からパスワードの入力を省略する」というような機能を提供するクッキーを使わないことも対策の一つです。HTML/URLインジェクション
サービス妨害
def safeMax = Math.max(params.max?.toInteger(), 100) // limit to 100 results return Book.list(max:safeMax)
def safeMax = Math.max(params.max?.toInteger(), 100) // 結果を100件に制限する return Book.list(max:safeMax)
推測可能なID
14.2 クロスサイトスクリプティング(XSS)防御
Cross Site Scripting (XSS) attacks are a common attack vector for web applications. They typically involve submitting HTML or Javascript code in a form such that when that code is displayed, the browser does something nasty. It could be as simple as popping up an alert box, or it could be much worse. The solution is to escape all untrusted user input when it is displayed in a page. For example,<script>alert('Got ya!');</script>
will become
<script>alert('Got ya!');</script>
when rendered, nullifying the effects of the malicious input.
By default, Grails plays it safe and escapes all content in ${}
expressions in GSPs. All the standard GSP tags are also safe by default, escaping any relevant attribute values.
So what happens when you want to stop Grails from escaping some content? There are valid use cases for putting HTML into the database and rendering it as-is, as long as that content is trusted. In such cases, you can tell Grails that the content is safe as should be rendered raw, i.e. without any escaping:
<section>${raw(page.content)}</section>
The raw()
method you see here is available from controllers, tag libraries and GSP pages.
Although Grails plays it safe by default, that is no guarantee that your application will be invulnerable to an XSS-style attack. Such an attack is less likely to succeed than would otherwise be the case, but developers should always be conscious of potential attack vectors and attempt to uncover vulnerabilities in the application during testing. It's also easy to switch to an unsafe default, thereby increasing the risk of a vulnerability being introduced.
It's difficult to make a solution that works for everyone, and so Grails provides a lot of flexibility with regard to fine-tuning how escaping works, allowing you to keep most of your application safe while switching off default escaping or changing the codec used for pages, tags, page fragments, and more.
Configuration
It is recommended that you review the configuration of a newly created Grails application to garner an understanding of XSS prevention works in Grails.
GSP features the ability to automatically HTML encode GSP expressions, and as of Grails 2.3 this is the default configuration. The default configuration (found in Config.groovy
) for a newly created Grails application can be seen below:
grails { views { gsp { encoding = 'UTF-8' htmlcodec = 'xml' // use xml escaping instead of HTML4 escaping codecs { expression = 'html' // escapes values inside ${} scriptlet = 'html' // escapes output from scriptlets in GSPs taglib = 'none' // escapes output from taglibs staticparts = 'none' // escapes output from static template parts } } // escapes all not-encoded output at final stage of outputting filteringCodecForContentType { //'text/html' = 'html' } } }
GSP features several codecs that it uses when writing the page to the response. The codecs are configured in the codecs
block and are described below:
expression
- The expression codec is used to encode any code found within ${..} expressions. The default for newly created application ishtml
encoding.scriptlet
- Used for output from GSP scriplets (<% %>, <%= %> blocks). The default for newly created applications ishtml
encodingtaglib
- Used to encode output from GSP tag libraries. The default isnone
for new applications, as typically it is the responsibility of the tag author to define the encoding of a given tag and by specifyingnone
Grails remains backwards compatible with older tag libraries.staticparts
- Used to encode the raw markup output by a GSP page. The default isnone
.
Double Encoding Prevention
Versions of Grails prior to 2.3, included the ability to set the default codec to html
, however enabling this setting sometimes proved problematic when using existing plugins due to encoding being applied twice (once by the html
codec and then again if the plugin manually called encodeAsHTML
).
Grails 2.3 includes double encoding prevention so that when an expression is evaluated, it will not encode if the data has already been encoded (Example ${foo.encodeAsHTML()}
).
Raw Output
If you are 100% sure that the value you wish to present on the page has not been received from user input, and you do not wish the value to be encoded then you can use the raw
method:
${raw(book.title)}
The 'raw' method is available in tag libraries, controllers and GSP pages.
Per Plugin Encoding
Grails also features the ability to control the codecs used on a per plugin basis. For example if you have a plugin named foo
installed, then placing the following configuration in your application's Config.groovy
will disable encoding for only the foo
plugin
foo.grails.views.gsp.codecs.expression = "none"
Per Page Encoding
You can also control the various codecs used to render a GSP page on a per page basis, using a page directive:
<%@page expressionCodec="none" %>
Per Tag Library Encoding
Each tag library created has the opportunity to specify a default codec used to encode output from the tag library using the "defaultEncodeAs" property:
static defaultEncodeAs = 'html'
Encoding can also be specified on a per tag basis using "encodeAsForTags":
static encodeAsForTags = [tagName: 'raw']
Context Sensitive Encoding Switching
Certain tags require certain encodings and Grails features the ability to enable a codec only a certain part of a tag's execution using the "withCodec" method. Consider for example the "<g:javascript>"" tag which allows you to embed JavaScript code in the page. This tag requires JavaScript encoding, not HTML coding for the execution of the body of the tag (but not for the markup that is output):
out.println '<script type="text/javascript">' withCodec("JavaScript") { out << body() } out.println() out.println '</script>'
Forced Encoding for Tags
If a tag specifies a default encoding that differs from your requirements you can force the encoding for any tag by passing the optional 'encodeAs' attribute:
<g:message code="foo.bar" encodeAs="JavaScript" />
Default Encoding for All Output
The default configuration for new applications is fine for most use cases, and backwards compatible with existing plugins and tag libraries. However, you can also make your application even more secure by configuring Grails to always encode all output at the end of a response. This is done using the filteringCodecForContentType
configuration in Config.groovy
:
grails.views.gsp.filteringCodecForContentType.'text/html' = 'html'
Note that, if activated, the staticparts
codec typically needs to be set to raw
so that static markup is not encoded:
codecs {
expression = 'html' // escapes values inside ${}
scriptlet = 'html' // escapes output from scriptlets in GSPs
taglib = 'none' // escapes output from taglibs
staticparts = 'raw' // escapes output from static template parts
}
14.3 エンコード・デコードオブジェクト
コーデッククラス
grails-app/utils/
directory.grails-app/utils/
ディレクトリ以下にあるコーデックを動的にロードします。grails-app/utils/
for class names that end with the convention Codec
. For example one of the standard codecs that ships with Grails is HTMLCodec
.grails-app/utils/
ディレクトリ以下にある、Codec
という名前で終わるクラスを探します。例えば、Grailsに標準で実装されているコーデックの一つにHTMLCodec
があります。encode
closure Grails will create a dynamic encode
method and add that method to the Object
class with a name representing the codec that defined the encode closure. For example, the HTMLCodec
class defines an encode
closure, so Grails attaches it with the name encodeAsHTML
.encode
クロージャが定義されている場合、Grailsは動的なencode
メソッドを作成し、そのクロージャを定義しているコーデックの名前でそのメソッドをObject
クラスに追加します。例えば、HTMLCodec
クラスではencode
クロージャが定義されているため、GrailsはencodeAsHTML
という名前でメソッドを追加します。HTMLCodec
and URLCodec
classes also define a decode
closure, so Grails attaches those with the names decodeHTML
and decodeURL
respectively. Dynamic codec methods may be invoked from anywhere in a Grails application. For example, consider a case where a report contains a property called 'description' which may contain special characters that must be escaped to be presented in an HTML document. One way to deal with that in a GSP is to encode the description property using the dynamic encode method as shown below:HTMLCodec
クラスとURLCodec
クラスではdecode
クロージャも定義されているので、GrailsはこれらをそれぞれdecodeHTML
, decodeURL
という名前で追加します。動的なコーデックメソッドはGrailsアプリケーションのどこからでも呼び出すことができます。例えば、report
がdescription
という名前のプロパティを持っているケースを想定します。description
は特殊文字を文字を含んでいる可能性があるため、HTMLドキュメントとして表示させる前にエスケープする必要があるとします。GSPでこれを実現する一つの方法は、以下のように、動的なエンコードメソッドを使用してdescription
プロパティをエンコードすることです:${report.description.encodeAsHTML()}
value.decodeHTML()
syntax.value.decodeHTML()
というような形で行うことができます。標準コーデック
<input name="comment.message" value="${comment.message.encodeAsHTML()}"/>
Note that the HTML encoding does not re-encode apostrophe/single quote so you must use double quotes on attribute values to avoid text with apostrophes affecting your page.HTMLエンコードでは、アポストロフィ/シングルクォートを再エンコードしないことに注意してください。エンコードする文字列にアポストロフィが入っている場合に備えて、属性の値はダブルクォートで括ってください。
<a href="/mycontroller/find?searchKey=${lastSearch.encodeAsURL()}">
Repeat last search
</a>
Your registration code is: ${user.registrationCode.encodeAsBase64()}
Element.update('${elementId}',
'${render(template: "/common/message").encodeAsJavaScript()}')
Selected colour: #${[255,127,255].encodeAsHex()}
Your API Key: ${user.uniqueID.encodeAsMD5()}
byte[] passwordHash = params.password.encodeAsMD5Bytes()
Your API Key: ${user.uniqueID.encodeAsSHA1()}
byte[] passwordHash = params.password.encodeAsSHA1Bytes()
Your API Key: ${user.uniqueID.encodeAsSHA256()}
byte[] passwordHash = params.password.encodeAsSHA256Bytes()
カスタムコーデック
grails-app/utils/
directory and the class name must end with Codec
. The codec may contain a static
encode
closure, a static
decode
closure or both. The closure must accept a single argument which will be the object that the dynamic method was invoked on. For Example:grails-app/utils/
ディレクトリ以下に配置され、かつ、クラス名はCodec
で終わる必要があります。コーデックにはstatic
encode
クロージャとstatic
decode
クロージャのいずれかまたは両方を定義することができます。これらのクロージャは、1引数 (動的なメソッドとして実行された際にエンコード/デコード対象のオブジェクトになります) を受け付ける必要があります。例えば、以下のように記述します:class PigLatinCodec { static encode = { str -> // convert the string to pig latin and return the result } }
${lastName.encodeAsPigLatin()}
14.4 認証
grails-app/conf/SecurityFilters.groovy
by running:grails-app/conf/SecurityFilters.groovy
というクラスに新しいフィルタセットが生成されます:grails create-filters security
class SecurityFilters { def filters = { loginCheck(controller: '*', action: '*') { before = { if (!session.user && actionName != "login") { redirect(controller: "user", action: "login") return false } } } } }
loginCheck
filter intercepts execution before all actions except login
are executed, and if there is no user in the session then redirect to the login
action.loginCheck
フィルタがすべてのアクションの実行前にインターセプトされます。ただし、セッションにユーザが存在しない状態でlogin
以外のアクションが呼び出された場合は、login
アクションにリダイレクトされます。login
action itself is simple too:login
アクションの実装も簡単です:def login() { if (request.get) { return // render the login view }def u = User.findByLogin(params.login) if (u) { if (u.password == params.password) { session.user = u redirect(action: "home") } else { render(view: "login", model: [message: "Password incorrect"]) } } else { render(view: "login", model: [message: "User not found"]) } }
14.5 セキュリティプラグイン
14.5.1 Spring Security
14.5.2 Shiro
JsecAuthBase
in each controller you want secured and then provide an accessControl
block to setup the roles. An example below:JsecAuthBase
という基底クラスを継承し、accessControl
ブロックを使用してロールを設定します。以下に例を示します:class ExampleController extends JsecAuthBase { static accessControl = { // All actions require the 'Observer' role. role(name: 'Observer')// The 'edit' action requires the 'Administrator' role. role(name: 'Administrator', action: 'edit')
// Alternatively, several actions can be specified. role(name: 'Administrator', only: [ 'create', 'edit', 'save', 'update' ]) } … }