Merge pull request #10316 from hmac/hmac/actionview

Ruby: Model ActionView
This commit is contained in:
Harry Maclean
2022-10-04 08:16:16 +13:00
committed by GitHub
12 changed files with 377 additions and 4 deletions

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Various XSS sinks in the ActionView library are now recognized.

View File

@@ -8,7 +8,7 @@ private import codeql.ruby.controlflow.CfgNodes
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.RemoteFlowSources
private import codeql.ruby.ApiGraphs
private import ActionView
private import codeql.ruby.frameworks.ActionView
private import codeql.ruby.frameworks.ActionDispatch
/**

View File

@@ -37,7 +37,7 @@ private class ActionViewHtmlSafeCall extends HtmlSafeCall {
*/
abstract class HtmlEscapeCall extends MethodCall {
// "h" is aliased to "html_escape" in ActiveSupport
HtmlEscapeCall() { this.getMethodName() = ["html_escape", "html_escape_once", "h"] }
HtmlEscapeCall() { this.getMethodName() = ["html_escape", "html_escape_once", "h", "sanitize"] }
}
/**
@@ -215,4 +215,138 @@ class FileSystemResolverAccess extends DataFlow::CallNode, FileSystemAccess::Ran
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
}
// TODO: model flow in/out of template files properly,
// TODO: Move the classes and predicates above inside this module.
/** Modeling for `ActionView`. */
module ActionView {
/**
* Action view helper methods which are XSS sinks.
*/
module Helpers {
/**
* A call to an ActionView helper which renders its argument without escaping.
* The argument should be treated as an XSS sink. In the documentation for
* classes in this module, the vulnerable argument is named `x`.
*/
abstract class RawHelperCall extends MethodCall {
/**
* Get an argument which is rendered without escaping.
*/
abstract Expr getRawArgument();
}
/**
* `ActionView::Helpers::TextHelper#simple_format`.
*
* `simple_format(x, y, sanitize: false)`.
*/
private class SimpleFormat extends ActionViewContextCall, RawHelperCall {
SimpleFormat() {
this.getMethodName() = "simple_format" and
this.getKeywordArgument("sanitize").getConstantValue().isBoolean(false)
}
override Expr getRawArgument() { result = this.getArgument(0) }
}
/**
* `ActionView::Helpers::TextHelper#truncate`.
*
* `truncate(x, escape: false)`.
*/
private class Truncate extends ActionViewContextCall, RawHelperCall {
Truncate() {
this.getMethodName() = "truncate" and
this.getKeywordArgument("escape").getConstantValue().isBoolean(false)
}
override Expr getRawArgument() { result = this.getArgument(0) }
}
/**
* `ActionView::Helpers::TextHelper#highlight`.
*
* `highlight(x, y, sanitize: false)`.
*/
private class Highlight extends ActionViewContextCall, RawHelperCall {
Highlight() {
this.getMethodName() = "highlight" and
this.getKeywordArgument("sanitize").getConstantValue().isBoolean(false)
}
override Expr getRawArgument() { result = this.getArgument(0) }
}
/**
* `ActionView::Helpers::JavascriptHelper#javascript_tag`.
*
* `javascript_tag(x)`.
*/
private class JavascriptTag extends ActionViewContextCall, RawHelperCall {
JavascriptTag() { this.getMethodName() = "javascript_tag" }
override Expr getRawArgument() { result = this.getArgument(0) }
}
/**
* `ActionView::Helpers::TagHelper#content_tag`.
*
* `content_tag(x, x, y, false)`.
*/
private class ContentTag extends ActionViewContextCall, RawHelperCall {
ContentTag() {
this.getMethodName() = "content_tag" and
this.getArgument(3).getConstantValue().isBoolean(false)
}
override Expr getRawArgument() { result = this.getArgument(1) }
}
/**
* `ActionView::Helpers::TagHelper#tag`.
*
* `tag(x, x, y, false)`.
*/
private class Tag extends ActionViewContextCall, RawHelperCall {
Tag() {
this.getMethodName() = "tag" and
this.getArgument(3).getConstantValue().isBoolean(false)
}
override Expr getRawArgument() { result = this.getArgument(0) }
}
/**
* `ActionView::Helpers::TagHelper#tag.<tag name>`.
*
* `tag.h1(x, escape: false)`.
*/
private class TagMethod extends MethodCall, RawHelperCall {
TagMethod() {
inActionViewContext(this) and
this.getReceiver().(MethodCall).getMethodName() = "tag" and
this.getKeywordArgument("escape").getConstantValue().isBoolean(false)
}
override Expr getRawArgument() { result = this.getArgument(0) }
}
}
/**
* An argument to a method call which constructs a script tag, interpreting the
* argument as a URL. Remote input flowing to this argument may allow loading of
* arbitrary javascript.
*/
class ArgumentInterpretedAsUrl extends DataFlow::Node {
ArgumentInterpretedAsUrl() {
exists(DataFlow::CallNode call |
call.getMethodName() = ["javascript_include_tag", "javascript_path", "path_to_javascript"] and
this = call.getArgument(0)
or
call.getMethodName() = "javascript_url" and
this = call.getKeywordArgument("host")
)
}
}
}

View File

@@ -9,6 +9,7 @@ private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.ApiGraphs
private import codeql.ruby.frameworks.stdlib.Logger::Logger as StdlibLogger
private import codeql.ruby.frameworks.data.ModelsAsData
/**
* Modeling for `ActiveSupport`.
@@ -138,4 +139,28 @@ module ActiveSupport {
}
}
}
/**
* `ActiveSupport::SafeBuffer` wraps a string, providing HTML-safe methods
* for concatenation.
* It is possible to insert tainted data into `SafeBuffer` that won't get
* sanitized, and this taint is then propagated via most of the methods.
*/
private class SafeBufferSummary extends ModelInput::SummaryModelCsv {
// TODO: SafeBuffer also reponds to all String methods.
// Can we model this without repeating all the existing summaries we have
// for String?
override predicate row(string row) {
row =
[
// SafeBuffer.new(x) does not sanitize x
"activesupport;;Member[ActionView].Member[SafeBuffer].Method[new];Argument[0];ReturnValue;taint",
// SafeBuffer#safe_concat(x) does not sanitize x
"activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[safe_concat];Argument[0];ReturnValue;taint",
"activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[safe_concat];Argument[0];Argument[self];taint",
// These methods preserve taint in self
"activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[concat,insert,prepend,to_s,to_param];Argument[self];ReturnValue;taint",
]
}
}
}

View File

@@ -75,6 +75,25 @@ private module Shared {
RawCallArgumentAsSink() { this.getCall() instanceof RawCall }
}
/**
* An argument to an ActionView helper method which is not escaped,
* considered as a flow sink.
*/
class RawHelperCallArgumentAsSink extends Sink {
RawHelperCallArgumentAsSink() {
exists(ErbOutputDirective d, ActionView::Helpers::RawHelperCall c |
d.getTerminalStmt() = c and this.asExpr().getExpr() = c.getRawArgument()
)
}
}
/**
* An argument that is used to construct the `src` attribute of a `<script>`
* tag.
*/
class ArgumentInterpretedAsUrlAsSink extends Sink, ErbOutputMethodCallArgumentNode,
ActionView::ArgumentInterpretedAsUrl { }
/**
* A argument to a call to the `link_to` method, which does not expect
* unsanitized user-input, considered as a flow sink.

View File

@@ -30,3 +30,12 @@ httpResponses
| app/controllers/foo/bars_controller.rb:36:12:36:67 | call to render_to_string | app/controllers/foo/bars_controller.rb:36:29:36:33 | @user | application/json |
| app/controllers/foo/bars_controller.rb:38:5:38:50 | call to render | app/controllers/foo/bars_controller.rb:38:12:38:22 | call to backtrace | text/plain |
| app/controllers/foo/bars_controller.rb:44:5:44:17 | call to render | app/controllers/foo/bars_controller.rb:44:12:44:17 | "show" | text/html |
rawHelperCalls
| action_view/helpers.erb:4:1:4:36 | call to simple_format | action_view/helpers.erb:4:15:4:15 | call to x |
| action_view/helpers.erb:7:1:7:26 | call to truncate | action_view/helpers.erb:7:10:7:10 | call to x |
| action_view/helpers.erb:10:1:10:29 | call to highlight | action_view/helpers.erb:10:11:10:11 | call to x |
| action_view/helpers.erb:12:1:12:17 | call to javascript_tag | action_view/helpers.erb:12:16:12:16 | call to x |
| action_view/helpers.erb:15:1:15:27 | call to content_tag | action_view/helpers.erb:15:16:15:16 | call to y |
| action_view/helpers.erb:18:1:18:19 | call to tag | action_view/helpers.erb:18:5:18:5 | call to x |
| action_view/helpers.erb:21:1:21:24 | call to h1 | action_view/helpers.erb:21:8:21:8 | call to x |
| action_view/helpers.erb:24:1:24:23 | call to p | action_view/helpers.erb:24:7:24:7 | call to x |

View File

@@ -1,7 +1,8 @@
private import ruby
private import codeql.ruby.AST
private import codeql.ruby.frameworks.ActionController
private import codeql.ruby.frameworks.ActionView
private import codeql.ruby.Concepts
private import codeql.ruby.DataFlow
query predicate htmlSafeCalls(HtmlSafeCall c) { any() }
@@ -16,3 +17,7 @@ query predicate linkToCalls(LinkToCall c) { any() }
query predicate httpResponses(Http::Server::HttpResponse r, DataFlow::Node body, string mimeType) {
r.getBody() = body and r.getMimetype() = mimeType
}
query predicate rawHelperCalls(ActionView::Helpers::RawHelperCall c, Expr arg) {
arg = c.getRawArgument()
}

View File

@@ -0,0 +1,26 @@
<%=
simple_format(x, y, sanitize: true)
simple_format(x, y, sanitize: false)
truncate(x, escape: true)
truncate(x, escape: false)
highlight(x, sanitize: true)
highlight(x, sanitize: false)
javascript_tag(x)
content_tag(x, y, z, true)
content_tag(x, y, z, false)
tag(x, y, z, true)
tag(x, y, z, false)
tag.h1(x, escape: true)
tag.h1(x, escape: false)
tag.p(x, escape: true)
tag.p(x, escape: false)
%>

View File

@@ -102,6 +102,40 @@ edges
| active_support.rb:134:10:134:10 | b [element] : | active_support.rb:134:10:134:13 | ...[...] |
| active_support.rb:135:10:135:10 | b [element] : | active_support.rb:135:10:135:13 | ...[...] |
| active_support.rb:135:10:135:10 | b [element] : | active_support.rb:135:10:135:13 | ...[...] |
| active_support.rb:139:7:139:16 | call to source : | active_support.rb:140:34:140:34 | x : |
| active_support.rb:140:7:140:35 | call to new : | active_support.rb:141:8:141:8 | y |
| active_support.rb:140:34:140:34 | x : | active_support.rb:140:7:140:35 | call to new : |
| active_support.rb:146:7:146:16 | call to source : | active_support.rb:147:21:147:21 | b : |
| active_support.rb:147:7:147:22 | call to safe_concat : | active_support.rb:148:8:148:8 | y |
| active_support.rb:147:21:147:21 | b : | active_support.rb:147:7:147:22 | call to safe_concat : |
| active_support.rb:153:7:153:16 | call to source : | active_support.rb:154:17:154:17 | b : |
| active_support.rb:154:3:154:3 | [post] x : | active_support.rb:155:8:155:8 | x |
| active_support.rb:154:17:154:17 | b : | active_support.rb:154:3:154:3 | [post] x : |
| active_support.rb:159:7:159:16 | call to source : | active_support.rb:161:34:161:34 | a : |
| active_support.rb:161:7:161:35 | call to new : | active_support.rb:162:7:162:7 | x : |
| active_support.rb:161:34:161:34 | a : | active_support.rb:161:7:161:35 | call to new : |
| active_support.rb:162:7:162:7 | x : | active_support.rb:162:7:162:17 | call to concat : |
| active_support.rb:162:7:162:17 | call to concat : | active_support.rb:163:8:163:8 | y |
| active_support.rb:167:7:167:16 | call to source : | active_support.rb:169:34:169:34 | a : |
| active_support.rb:169:7:169:35 | call to new : | active_support.rb:170:7:170:7 | x : |
| active_support.rb:169:34:169:34 | a : | active_support.rb:169:7:169:35 | call to new : |
| active_support.rb:170:7:170:7 | x : | active_support.rb:170:7:170:20 | call to insert : |
| active_support.rb:170:7:170:20 | call to insert : | active_support.rb:171:8:171:8 | y |
| active_support.rb:175:7:175:16 | call to source : | active_support.rb:177:34:177:34 | a : |
| active_support.rb:177:7:177:35 | call to new : | active_support.rb:178:7:178:7 | x : |
| active_support.rb:177:34:177:34 | a : | active_support.rb:177:7:177:35 | call to new : |
| active_support.rb:178:7:178:7 | x : | active_support.rb:178:7:178:18 | call to prepend : |
| active_support.rb:178:7:178:18 | call to prepend : | active_support.rb:179:8:179:8 | y |
| active_support.rb:183:7:183:16 | call to source : | active_support.rb:184:34:184:34 | a : |
| active_support.rb:184:7:184:35 | call to new : | active_support.rb:185:7:185:7 | x : |
| active_support.rb:184:34:184:34 | a : | active_support.rb:184:7:184:35 | call to new : |
| active_support.rb:185:7:185:7 | x : | active_support.rb:185:7:185:12 | call to to_s : |
| active_support.rb:185:7:185:12 | call to to_s : | active_support.rb:186:8:186:8 | y |
| active_support.rb:190:7:190:16 | call to source : | active_support.rb:191:34:191:34 | a : |
| active_support.rb:191:7:191:35 | call to new : | active_support.rb:192:7:192:7 | x : |
| active_support.rb:191:34:191:34 | a : | active_support.rb:191:7:191:35 | call to new : |
| active_support.rb:192:7:192:7 | x : | active_support.rb:192:7:192:16 | call to to_param : |
| active_support.rb:192:7:192:16 | call to to_param : | active_support.rb:193:8:193:8 | y |
nodes
| active_support.rb:9:9:9:18 | call to source : | semmle.label | call to source : |
| active_support.rb:10:10:10:10 | x : | semmle.label | x : |
@@ -234,6 +268,48 @@ nodes
| active_support.rb:135:10:135:10 | b [element] : | semmle.label | b [element] : |
| active_support.rb:135:10:135:13 | ...[...] | semmle.label | ...[...] |
| active_support.rb:135:10:135:13 | ...[...] | semmle.label | ...[...] |
| active_support.rb:139:7:139:16 | call to source : | semmle.label | call to source : |
| active_support.rb:140:7:140:35 | call to new : | semmle.label | call to new : |
| active_support.rb:140:34:140:34 | x : | semmle.label | x : |
| active_support.rb:141:8:141:8 | y | semmle.label | y |
| active_support.rb:146:7:146:16 | call to source : | semmle.label | call to source : |
| active_support.rb:147:7:147:22 | call to safe_concat : | semmle.label | call to safe_concat : |
| active_support.rb:147:21:147:21 | b : | semmle.label | b : |
| active_support.rb:148:8:148:8 | y | semmle.label | y |
| active_support.rb:153:7:153:16 | call to source : | semmle.label | call to source : |
| active_support.rb:154:3:154:3 | [post] x : | semmle.label | [post] x : |
| active_support.rb:154:17:154:17 | b : | semmle.label | b : |
| active_support.rb:155:8:155:8 | x | semmle.label | x |
| active_support.rb:159:7:159:16 | call to source : | semmle.label | call to source : |
| active_support.rb:161:7:161:35 | call to new : | semmle.label | call to new : |
| active_support.rb:161:34:161:34 | a : | semmle.label | a : |
| active_support.rb:162:7:162:7 | x : | semmle.label | x : |
| active_support.rb:162:7:162:17 | call to concat : | semmle.label | call to concat : |
| active_support.rb:163:8:163:8 | y | semmle.label | y |
| active_support.rb:167:7:167:16 | call to source : | semmle.label | call to source : |
| active_support.rb:169:7:169:35 | call to new : | semmle.label | call to new : |
| active_support.rb:169:34:169:34 | a : | semmle.label | a : |
| active_support.rb:170:7:170:7 | x : | semmle.label | x : |
| active_support.rb:170:7:170:20 | call to insert : | semmle.label | call to insert : |
| active_support.rb:171:8:171:8 | y | semmle.label | y |
| active_support.rb:175:7:175:16 | call to source : | semmle.label | call to source : |
| active_support.rb:177:7:177:35 | call to new : | semmle.label | call to new : |
| active_support.rb:177:34:177:34 | a : | semmle.label | a : |
| active_support.rb:178:7:178:7 | x : | semmle.label | x : |
| active_support.rb:178:7:178:18 | call to prepend : | semmle.label | call to prepend : |
| active_support.rb:179:8:179:8 | y | semmle.label | y |
| active_support.rb:183:7:183:16 | call to source : | semmle.label | call to source : |
| active_support.rb:184:7:184:35 | call to new : | semmle.label | call to new : |
| active_support.rb:184:34:184:34 | a : | semmle.label | a : |
| active_support.rb:185:7:185:7 | x : | semmle.label | x : |
| active_support.rb:185:7:185:12 | call to to_s : | semmle.label | call to to_s : |
| active_support.rb:186:8:186:8 | y | semmle.label | y |
| active_support.rb:190:7:190:16 | call to source : | semmle.label | call to source : |
| active_support.rb:191:7:191:35 | call to new : | semmle.label | call to new : |
| active_support.rb:191:34:191:34 | a : | semmle.label | a : |
| active_support.rb:192:7:192:7 | x : | semmle.label | x : |
| active_support.rb:192:7:192:16 | call to to_param : | semmle.label | call to to_param : |
| active_support.rb:193:8:193:8 | y | semmle.label | y |
subpaths
#select
| active_support.rb:106:10:106:13 | ...[...] | active_support.rb:104:10:104:17 | call to source : | active_support.rb:106:10:106:13 | ...[...] | $@ | active_support.rb:104:10:104:17 | call to source : | call to source : |

View File

@@ -133,4 +133,62 @@ def m_including
sink b[1] # $ hasValueFlow=3 $ hasValueFlow=4
sink b[2] # $ hasValueFlow=3 $ hasValueFlow=4
sink b[3] # $ hasValueFlow=3 $ hasValueFlow=4
end
end
def m_safe_buffer_new
x = source "a"
y = ActionView::SafeBuffer.new(x)
sink y # $hasTaintFlow=a
end
def m_safe_buffer_safe_concat_retval
x = ActionView::SafeBuffer.new("a")
b = source "b"
y = x.safe_concat(b)
sink y # $hasTaintFlow=b
end
def m_safe_buffer_safe_concat_self
x = ActionView::SafeBuffer.new("a")
b = source "b"
x.safe_concat(b)
sink x # $hasTaintFlow=b
end
def m_safe_buffer_concat
a = source "a"
b = source "b"
x = ActionView::SafeBuffer.new(a)
y = x.concat(b)
sink y # $hasTaintFlow=a
end
def m_safe_buffer_insert
a = source "a"
b = source "b"
x = ActionView::SafeBuffer.new(a)
y = x.insert(i, b)
sink y # $hasTaintFlow=a
end
def m_safe_buffer_prepend
a = source "a"
b = source "b"
x = ActionView::SafeBuffer.new(a)
y = x.prepend(b)
sink y # $hasTaintFlow=a
end
def m_safe_buffer_to_s
a = source "a"
x = ActionView::SafeBuffer.new(a)
y = x.to_s
sink y # $hasTaintFlow=a
end
def m_safe_buffer_to_param
a = source "a"
x = ActionView::SafeBuffer.new(a)
y = x.to_param
sink y # $hasTaintFlow=a
end

View File

@@ -22,6 +22,8 @@ edges
| app/views/foo/bars/show.html.erb:44:76:44:87 | call to display_text : | app/views/foo/bars/show.html.erb:44:64:44:87 | ... + ... : |
| app/views/foo/bars/show.html.erb:54:29:54:34 | call to params : | app/views/foo/bars/show.html.erb:54:29:54:44 | ...[...] |
| app/views/foo/bars/show.html.erb:57:13:57:18 | call to params : | app/views/foo/bars/show.html.erb:57:13:57:28 | ...[...] |
| app/views/foo/bars/show.html.erb:74:19:74:24 | call to params : | app/views/foo/bars/show.html.erb:74:19:74:34 | ...[...] |
| app/views/foo/bars/show.html.erb:77:28:77:33 | call to params : | app/views/foo/bars/show.html.erb:77:28:77:39 | ...[...] |
nodes
| app/controllers/foo/bars_controller.rb:9:12:9:17 | call to params : | semmle.label | call to params : |
| app/controllers/foo/bars_controller.rb:9:12:9:29 | ...[...] : | semmle.label | ...[...] : |
@@ -50,6 +52,10 @@ nodes
| app/views/foo/bars/show.html.erb:54:29:54:44 | ...[...] | semmle.label | ...[...] |
| app/views/foo/bars/show.html.erb:57:13:57:18 | call to params : | semmle.label | call to params : |
| app/views/foo/bars/show.html.erb:57:13:57:28 | ...[...] | semmle.label | ...[...] |
| app/views/foo/bars/show.html.erb:74:19:74:24 | call to params : | semmle.label | call to params : |
| app/views/foo/bars/show.html.erb:74:19:74:34 | ...[...] | semmle.label | ...[...] |
| app/views/foo/bars/show.html.erb:77:28:77:33 | call to params : | semmle.label | call to params : |
| app/views/foo/bars/show.html.erb:77:28:77:39 | ...[...] | semmle.label | ...[...] |
subpaths
#select
| app/views/foo/bars/_widget.html.erb:5:9:5:20 | call to display_text | app/controllers/foo/bars_controller.rb:18:10:18:15 | call to params : | app/views/foo/bars/_widget.html.erb:5:9:5:20 | call to display_text | Cross-site scripting vulnerability due to a $@. | app/controllers/foo/bars_controller.rb:18:10:18:15 | call to params | user-provided value |
@@ -64,3 +70,5 @@ subpaths
| app/views/foo/bars/show.html.erb:51:5:51:18 | call to user_name_memo | app/controllers/foo/bars_controller.rb:13:20:13:25 | call to params : | app/views/foo/bars/show.html.erb:51:5:51:18 | call to user_name_memo | Cross-site scripting vulnerability due to a $@. | app/controllers/foo/bars_controller.rb:13:20:13:25 | call to params | user-provided value |
| app/views/foo/bars/show.html.erb:54:29:54:44 | ...[...] | app/views/foo/bars/show.html.erb:54:29:54:34 | call to params : | app/views/foo/bars/show.html.erb:54:29:54:44 | ...[...] | Cross-site scripting vulnerability due to a $@. | app/views/foo/bars/show.html.erb:54:29:54:34 | call to params | user-provided value |
| app/views/foo/bars/show.html.erb:57:13:57:28 | ...[...] | app/views/foo/bars/show.html.erb:57:13:57:18 | call to params : | app/views/foo/bars/show.html.erb:57:13:57:28 | ...[...] | Cross-site scripting vulnerability due to a $@. | app/views/foo/bars/show.html.erb:57:13:57:18 | call to params | user-provided value |
| app/views/foo/bars/show.html.erb:74:19:74:34 | ...[...] | app/views/foo/bars/show.html.erb:74:19:74:24 | call to params : | app/views/foo/bars/show.html.erb:74:19:74:34 | ...[...] | Cross-site scripting vulnerability due to a $@. | app/views/foo/bars/show.html.erb:74:19:74:24 | call to params | user-provided value |
| app/views/foo/bars/show.html.erb:77:28:77:39 | ...[...] | app/views/foo/bars/show.html.erb:77:28:77:33 | call to params : | app/views/foo/bars/show.html.erb:77:28:77:39 | ...[...] | Cross-site scripting vulnerability due to a $@. | app/views/foo/bars/show.html.erb:77:28:77:33 | call to params | user-provided value |

View File

@@ -69,3 +69,12 @@
html_escaped_in_template = h params[:text]
html_escaped_in_template.html_safe
%>
<%# BAD: simple_format called with sanitize: false %>
<%= simple_format(params[:comment], sanitize: false) %>
<%# BAD: javasript_include_tag called with remote input %>
<%= javascript_include_tag params[:url] %>
<%# GOOD: input is sanitized %>
<%= sanitize(params[:comment]).html_safe %>