mirror of
https://github.com/github/codeql.git
synced 2026-04-30 11:15:13 +02:00
6
ruby/ql/lib/change-notes/2023-03-13-sinatra.md
Normal file
6
ruby/ql/lib/change-notes/2023-03-13-sinatra.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Accesses of `params` in Sinatra applications are now recognised as HTTP input accesses.
|
||||
* Data flow is tracked from Sinatra route handlers to ERB files.
|
||||
* Data flow is tracked between basic Sinatra filters (those without URL patterns) and their corresponding route handlers.
|
||||
@@ -27,4 +27,5 @@ private import codeql.ruby.frameworks.ActionDispatch
|
||||
private import codeql.ruby.frameworks.PosixSpawn
|
||||
private import codeql.ruby.frameworks.StringFormatters
|
||||
private import codeql.ruby.frameworks.Json
|
||||
private import codeql.ruby.frameworks.Sinatra
|
||||
private import codeql.ruby.frameworks.Twirp
|
||||
|
||||
@@ -294,6 +294,24 @@ module Ssa {
|
||||
override Location getLocation() { result = this.getBasicBlock().getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An SSA definition inserted at the beginning of a scope to represent a captured `self` variable.
|
||||
* For example, in
|
||||
*
|
||||
* ```rb
|
||||
* def m(x)
|
||||
* x.tap do |x|
|
||||
* foo(x)
|
||||
* end
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* an entry definition for `self` is inserted at the start of the `do` block.
|
||||
*/
|
||||
class CapturedSelfDefinition extends CapturedEntryDefinition {
|
||||
CapturedSelfDefinition() { this.getSourceVariable() instanceof SelfVariable }
|
||||
}
|
||||
|
||||
/**
|
||||
* An SSA definition inserted at a call that may update the value of a captured
|
||||
* variable. For example, in
|
||||
|
||||
300
ruby/ql/lib/codeql/ruby/frameworks/Sinatra.qll
Normal file
300
ruby/ql/lib/codeql/ruby/frameworks/Sinatra.qll
Normal file
@@ -0,0 +1,300 @@
|
||||
/** Provides modeling for the Sinatra library. */
|
||||
|
||||
private import codeql.ruby.controlflow.CfgNodes::ExprNodes
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate
|
||||
private import codeql.ruby.dataflow.SSA
|
||||
|
||||
/** Provides modeling for the Sinatra library. */
|
||||
module Sinatra {
|
||||
/**
|
||||
* A Sinatra application.
|
||||
*
|
||||
* ```rb
|
||||
* class MyApp < Sinatra::Base
|
||||
* get "/" do
|
||||
* erb :home
|
||||
* end
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
class App extends DataFlow::ClassNode {
|
||||
App() { this = DataFlow::getConstant("Sinatra").getConstant("Base").getADescendentModule() }
|
||||
|
||||
/**
|
||||
* Gets a route defined in this application.
|
||||
*/
|
||||
Route getARoute() { result.getApp() = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Sinatra route handler. HTTP requests with a matching method and path will
|
||||
* be handled by the block. For example, the following route will handle `GET`
|
||||
* requests with path `/`.
|
||||
*
|
||||
* ```rb
|
||||
* get "/" do
|
||||
* erb :home
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
class Route extends DataFlow::CallNode {
|
||||
private App app;
|
||||
|
||||
Route() {
|
||||
this =
|
||||
app.getAModuleLevelCall([
|
||||
"get", "post", "put", "patch", "delete", "options", "link", "unlink"
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the application that defines this route.
|
||||
*/
|
||||
App getApp() { result = app }
|
||||
|
||||
/**
|
||||
* Gets the body of this route.
|
||||
*/
|
||||
DataFlow::BlockNode getBody() { result = this.getBlock() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An access to the parameters of an HTTP request in a Sinatra route handler or callback.
|
||||
*/
|
||||
private class Params extends DataFlow::CallNode, Http::Server::RequestInputAccess::Range {
|
||||
Params() {
|
||||
this.asExpr().getExpr().getEnclosingCallable() =
|
||||
[any(Route r).getBody(), any(Filter f).getBody()].asCallableAstNode() and
|
||||
this.getMethodName() = "params"
|
||||
}
|
||||
|
||||
override string getSourceType() { result = "Sinatra::Base#params" }
|
||||
|
||||
override Http::Server::RequestInputKind getKind() {
|
||||
result = Http::Server::parameterInputKind()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call which renders an ERB template as an HTTP response.
|
||||
*/
|
||||
class ErbCall extends DataFlow::CallNode {
|
||||
private Route route;
|
||||
|
||||
ErbCall() {
|
||||
this.asExpr().getExpr().getEnclosingCallable() = route.getBody().asCallableAstNode() and
|
||||
this.getMethodName() = "erb"
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the template file corresponding to this call.
|
||||
*/
|
||||
ErbFile getTemplateFile() { result = getTemplateFile(this.asExpr().getExpr()) }
|
||||
|
||||
/**
|
||||
* Gets the route containing this call.
|
||||
*/
|
||||
Route getRoute() { result = route }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the template file referred to by `erbCall`.
|
||||
* This works on the AST level to avoid non-monotonic reecursion in `ErbLocalsHashSyntheticGlobal`.
|
||||
*/
|
||||
private ErbFile getTemplateFile(MethodCall erbCall) {
|
||||
erbCall.getMethodName() = "erb" and
|
||||
result.getTemplateName() = erbCall.getArgument(0).getConstantValue().getStringlikeValue() and
|
||||
result.getRelativePath().matches("%views/%")
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `Location.toString`, but displays the relative path rather than the full path.
|
||||
*/
|
||||
private string locationRelativePathToString(Location loc) {
|
||||
result =
|
||||
loc.getFile().getRelativePath() + "@" + loc.getStartLine() + ":" + loc.getStartColumn() + ":" +
|
||||
loc.getEndLine() + ":" + loc.getEndColumn()
|
||||
}
|
||||
|
||||
/**
|
||||
* A synthetic global representing the hash of local variables passed to an ERB template.
|
||||
*/
|
||||
class ErbLocalsHashSyntheticGlobal extends SummaryComponent::SyntheticGlobal {
|
||||
private string id;
|
||||
private MethodCall erbCall;
|
||||
private ErbFile erbFile;
|
||||
|
||||
ErbLocalsHashSyntheticGlobal() {
|
||||
this = "SinatraErbLocalsHash(" + id + ")" and
|
||||
id = erbFile.getRelativePath() + "," + locationRelativePathToString(erbCall.getLocation()) and
|
||||
erbCall.getMethodName() = "erb" and
|
||||
erbFile = getTemplateFile(erbCall)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `erb` call associated with this global.
|
||||
*/
|
||||
MethodCall getErbCall() { result = erbCall }
|
||||
|
||||
/**
|
||||
* Gets the ERB template that this global contains the locals for.
|
||||
*/
|
||||
ErbFile getErbFile() { result = erbFile }
|
||||
|
||||
/**
|
||||
* Gets a unique identifer for this global.
|
||||
*/
|
||||
string getId() { result = id }
|
||||
}
|
||||
|
||||
/**
|
||||
* A summary for `Sinatra::Base#erb`. This models the first half of the flow
|
||||
* from the `locals` keyword argument to variables in the ERB template. The
|
||||
* second half is modeled by `ErbLocalsAccessSummary`.
|
||||
*/
|
||||
private class ErbLocalsSummary extends SummarizedCallable {
|
||||
ErbLocalsSummary() { this = "Sinatra::Base#erb" }
|
||||
|
||||
override MethodCall getACall() { result = any(ErbCall c).asExpr().getExpr() }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[locals:]" and
|
||||
output = "SyntheticGlobal[" + any(ErbLocalsHashSyntheticGlobal global) + "]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A summary for accessing a local variable in an ERB template.
|
||||
* This is the second half of the modeling of the flow from the `locals`
|
||||
* keyword argument to variables in the ERB template.
|
||||
* The first half is modeled by `ErbLocalsSummary`.
|
||||
*/
|
||||
private class ErbLocalsAccessSummary extends SummarizedCallable {
|
||||
private ErbLocalsHashSyntheticGlobal global;
|
||||
private string local;
|
||||
|
||||
ErbLocalsAccessSummary() {
|
||||
this = "sinatra_erb_locals_access()" + global.getId() + "#" + local and
|
||||
local = any(MethodCall c | c.getLocation().getFile() = global.getErbFile()).getMethodName() and
|
||||
local = any(Pair p).getKey().getConstantValue().getStringlikeValue()
|
||||
}
|
||||
|
||||
override MethodCall getACall() {
|
||||
result.getLocation().getFile() = global.getErbFile() and
|
||||
result.getMethodName() = local and
|
||||
result.getReceiver() instanceof SelfVariableReadAccess
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "SyntheticGlobal[" + global + "].Element[:" + local + "]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class representing Sinatra filters AKA callbacks.
|
||||
*
|
||||
* Filters are run before or after the route handler. They can modify the
|
||||
* request and response, and share instance variables with the route handler.
|
||||
*/
|
||||
class Filter extends DataFlow::CallNode {
|
||||
private App app;
|
||||
|
||||
Filter() { this = app.getAModuleLevelCall(["before", "after"]) }
|
||||
|
||||
/** Gets the app which this filter belongs to. */
|
||||
App getApp() { result = app }
|
||||
|
||||
/**
|
||||
* Gets the pattern which constrains this route, if any. In the example below, the pattern is `/protected/*`.
|
||||
* Patterns are typically given as strings, and are interpreted by the `mustermann` gem (they are not regular expressions).
|
||||
* ```rb
|
||||
* before '/protected/*' do
|
||||
* authenticate!
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
DataFlow::ExprNode getPattern() { result = this.getArgument(0) }
|
||||
|
||||
/**
|
||||
* Holds if this filter has a pattern.
|
||||
*/
|
||||
predicate hasPattern() { exists(this.getPattern()) }
|
||||
|
||||
/**
|
||||
* Gets the body of this filter.
|
||||
*/
|
||||
DataFlow::BlockNode getBody() { result = this.getBlock() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A class for Sinatra `before` filters. These run before the route handler.
|
||||
*/
|
||||
class BeforeFilter extends Filter {
|
||||
BeforeFilter() { this.getMethodName() = "before" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A class for Sinatra `after` filters. These run after the route handler.
|
||||
*/
|
||||
class AfterFilter extends Filter {
|
||||
AfterFilter() { this.getMethodName() = "after" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A class defining additional jump steps arising from filters.
|
||||
* This only models flow between filters with no patterns - i.e. those that apply to all routes.
|
||||
* Filters with patterns are not yet modeled.
|
||||
*/
|
||||
class FilterJumpStep extends DataFlowPrivate::AdditionalJumpStep {
|
||||
/**
|
||||
* Holds if data can flow from `pred` to `succ` via a callback chain.
|
||||
*/
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(BeforeFilter filter, Route route |
|
||||
// the filter and route belong to the same app
|
||||
filter.getApp() = route.getApp() and
|
||||
// the filter applies to all routes
|
||||
not filter.hasPattern() and
|
||||
selfPostUpdate(pred, filter.getApp(), filter.getBody().asExpr().getExpr()) and
|
||||
blockCapturedSelfParameterNode(succ, route.getBody().asExpr().getExpr())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `n` is a post-update node for the `self` parameter of `app` in block `b`.
|
||||
*
|
||||
* In this example, `n` is the post-update node for `@foo = 1`.
|
||||
* ```rb
|
||||
* class MyApp < Sinatra::Base
|
||||
* before do
|
||||
* @foo = 1
|
||||
* end
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
private predicate selfPostUpdate(DataFlow::PostUpdateNode n, App app, Block b) {
|
||||
n.getPreUpdateNode().asExpr().getExpr() =
|
||||
any(SelfVariableAccess self |
|
||||
pragma[only_bind_into](b) = self.getEnclosingCallable() and
|
||||
self.getVariable().getDeclaringScope() = app.getADeclaration()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `n` is a node representing the `self` parameter captured by block `b`.
|
||||
*/
|
||||
private predicate blockCapturedSelfParameterNode(DataFlow::Node n, Block b) {
|
||||
exists(Ssa::CapturedSelfDefinition d |
|
||||
n.(DataFlowPrivate::SsaDefinitionExtNode).getDefinitionExt() = d and
|
||||
d.getBasicBlock().getScope() = b
|
||||
)
|
||||
}
|
||||
}
|
||||
25
ruby/ql/test/library-tests/frameworks/sinatra/Flow.expected
Normal file
25
ruby/ql/test/library-tests/frameworks/sinatra/Flow.expected
Normal file
@@ -0,0 +1,25 @@
|
||||
failures
|
||||
| views/index.erb:2:10:2:12 | call to foo | Unexpected result: hasTaintFlow= |
|
||||
edges
|
||||
| app.rb:75:5:75:8 | [post] self [@foo] : | app.rb:76:32:76:35 | self [@foo] : |
|
||||
| app.rb:75:12:75:17 | call to params : | app.rb:75:12:75:24 | ...[...] : |
|
||||
| app.rb:75:12:75:24 | ...[...] : | app.rb:75:5:75:8 | [post] self [@foo] : |
|
||||
| app.rb:76:32:76:35 | @foo : | views/index.erb:2:10:2:12 | call to foo |
|
||||
| app.rb:76:32:76:35 | self [@foo] : | app.rb:76:32:76:35 | @foo : |
|
||||
| app.rb:95:10:95:14 | self [@user] : | app.rb:95:10:95:14 | @user |
|
||||
| app.rb:103:5:103:9 | [post] self [@user] : | app.rb:95:10:95:14 | self [@user] : |
|
||||
| app.rb:103:13:103:22 | call to source : | app.rb:103:5:103:9 | [post] self [@user] : |
|
||||
nodes
|
||||
| app.rb:75:5:75:8 | [post] self [@foo] : | semmle.label | [post] self [@foo] : |
|
||||
| app.rb:75:12:75:17 | call to params : | semmle.label | call to params : |
|
||||
| app.rb:75:12:75:24 | ...[...] : | semmle.label | ...[...] : |
|
||||
| app.rb:76:32:76:35 | @foo : | semmle.label | @foo : |
|
||||
| app.rb:76:32:76:35 | self [@foo] : | semmle.label | self [@foo] : |
|
||||
| app.rb:95:10:95:14 | @user | semmle.label | @user |
|
||||
| app.rb:95:10:95:14 | self [@user] : | semmle.label | self [@user] : |
|
||||
| app.rb:103:5:103:9 | [post] self [@user] : | semmle.label | [post] self [@user] : |
|
||||
| app.rb:103:13:103:22 | call to source : | semmle.label | call to source : |
|
||||
| views/index.erb:2:10:2:12 | call to foo | semmle.label | call to foo |
|
||||
subpaths
|
||||
#select
|
||||
| views/index.erb:2:10:2:12 | call to foo | app.rb:75:12:75:17 | call to params : | views/index.erb:2:10:2:12 | call to foo | $@ | app.rb:75:12:75:17 | call to params : | call to params : |
|
||||
19
ruby/ql/test/library-tests/frameworks/sinatra/Flow.ql
Normal file
19
ruby/ql/test/library-tests/frameworks/sinatra/Flow.ql
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @kind path-problem
|
||||
*/
|
||||
|
||||
import ruby
|
||||
import TestUtilities.InlineFlowTest
|
||||
import PathGraph
|
||||
import codeql.ruby.frameworks.Sinatra
|
||||
import codeql.ruby.Concepts
|
||||
|
||||
class SinatraConf extends DefaultTaintFlowConf {
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source instanceof Http::Server::RequestInputAccess::Range
|
||||
}
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, SinatraConf conf
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select sink, source, sink, "$@", source, source.toString()
|
||||
@@ -0,0 +1,93 @@
|
||||
routes
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:2:3:4:5 | call to get |
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:6:3:8:5 | call to get |
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:10:3:13:5 | call to get |
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:15:3:18:5 | call to get |
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:20:3:22:5 | call to get |
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:24:3:26:5 | call to get |
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:28:3:31:5 | call to get |
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:33:3:35:5 | call to get |
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:37:3:42:5 | call to get |
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:44:3:46:5 | call to get |
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:48:3:50:5 | call to get |
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:52:3:54:5 | call to get |
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:56:3:58:5 | call to get |
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:60:3:62:5 | call to get |
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:66:3:68:5 | call to get |
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:70:3:72:5 | call to get |
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:74:3:77:5 | call to get |
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:79:3:82:5 | call to get |
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:89:3:92:5 | call to get |
|
||||
| app.rb:1:1:114:3 | MyApp | app.rb:94:3:96:5 | call to get |
|
||||
params
|
||||
| app.rb:3:14:3:19 | call to params |
|
||||
| app.rb:12:5:12:10 | call to params |
|
||||
| app.rb:17:5:17:10 | call to params |
|
||||
| app.rb:25:15:25:20 | call to params |
|
||||
| app.rb:39:13:39:18 | call to params |
|
||||
| app.rb:40:14:40:19 | call to params |
|
||||
| app.rb:45:38:45:43 | call to params |
|
||||
| app.rb:75:12:75:17 | call to params |
|
||||
| app.rb:91:5:91:10 | call to params |
|
||||
erbCalls
|
||||
| app.rb:76:5:76:36 | call to erb | views/index.erb:0:0:0:0 | views/index.erb |
|
||||
erbSyntheticGlobals
|
||||
| SinatraErbLocalsHash(library-tests/frameworks/sinatra/views/index.erb,library-tests/frameworks/sinatra/app.rb@76:5:76:36) | views/index.erb:0:0:0:0 | views/index.erb |
|
||||
filters
|
||||
| app.rb:84:3:87:5 | call to before | before |
|
||||
| app.rb:98:3:100:5 | call to after | after |
|
||||
| app.rb:102:3:104:5 | call to before | before |
|
||||
| app.rb:106:3:108:5 | call to before | before |
|
||||
| app.rb:111:3:113:5 | call to after | after |
|
||||
filterPatterns
|
||||
| app.rb:106:3:108:5 | call to before | app.rb:106:10:106:23 | "/protected/*" |
|
||||
| app.rb:111:3:113:5 | call to after | app.rb:111:9:111:23 | "/create/:slug" |
|
||||
additionalFlowSteps
|
||||
| app.rb:85:5:85:9 | [post] self | app.rb:2:22:4:5 | <captured> self |
|
||||
| app.rb:85:5:85:9 | [post] self | app.rb:10:21:13:5 | <captured> self |
|
||||
| app.rb:85:5:85:9 | [post] self | app.rb:15:23:18:5 | <captured> self |
|
||||
| app.rb:85:5:85:9 | [post] self | app.rb:24:26:26:5 | <captured> self |
|
||||
| app.rb:85:5:85:9 | [post] self | app.rb:37:16:42:5 | <captured> self |
|
||||
| app.rb:85:5:85:9 | [post] self | app.rb:44:53:46:5 | <captured> self |
|
||||
| app.rb:85:5:85:9 | [post] self | app.rb:56:32:58:5 | <captured> self |
|
||||
| app.rb:85:5:85:9 | [post] self | app.rb:60:48:62:5 | <captured> self |
|
||||
| app.rb:85:5:85:9 | [post] self | app.rb:74:11:77:5 | <captured> self |
|
||||
| app.rb:85:5:85:9 | [post] self | app.rb:79:11:82:5 | <captured> self |
|
||||
| app.rb:85:5:85:9 | [post] self | app.rb:89:16:92:5 | <captured> self |
|
||||
| app.rb:85:5:85:9 | [post] self | app.rb:94:15:96:5 | <captured> self |
|
||||
| app.rb:86:5:86:11 | [post] self | app.rb:2:22:4:5 | <captured> self |
|
||||
| app.rb:86:5:86:11 | [post] self | app.rb:10:21:13:5 | <captured> self |
|
||||
| app.rb:86:5:86:11 | [post] self | app.rb:15:23:18:5 | <captured> self |
|
||||
| app.rb:86:5:86:11 | [post] self | app.rb:24:26:26:5 | <captured> self |
|
||||
| app.rb:86:5:86:11 | [post] self | app.rb:37:16:42:5 | <captured> self |
|
||||
| app.rb:86:5:86:11 | [post] self | app.rb:44:53:46:5 | <captured> self |
|
||||
| app.rb:86:5:86:11 | [post] self | app.rb:56:32:58:5 | <captured> self |
|
||||
| app.rb:86:5:86:11 | [post] self | app.rb:60:48:62:5 | <captured> self |
|
||||
| app.rb:86:5:86:11 | [post] self | app.rb:74:11:77:5 | <captured> self |
|
||||
| app.rb:86:5:86:11 | [post] self | app.rb:79:11:82:5 | <captured> self |
|
||||
| app.rb:86:5:86:11 | [post] self | app.rb:89:16:92:5 | <captured> self |
|
||||
| app.rb:86:5:86:11 | [post] self | app.rb:94:15:96:5 | <captured> self |
|
||||
| app.rb:103:5:103:9 | [post] self | app.rb:2:22:4:5 | <captured> self |
|
||||
| app.rb:103:5:103:9 | [post] self | app.rb:10:21:13:5 | <captured> self |
|
||||
| app.rb:103:5:103:9 | [post] self | app.rb:15:23:18:5 | <captured> self |
|
||||
| app.rb:103:5:103:9 | [post] self | app.rb:24:26:26:5 | <captured> self |
|
||||
| app.rb:103:5:103:9 | [post] self | app.rb:37:16:42:5 | <captured> self |
|
||||
| app.rb:103:5:103:9 | [post] self | app.rb:44:53:46:5 | <captured> self |
|
||||
| app.rb:103:5:103:9 | [post] self | app.rb:56:32:58:5 | <captured> self |
|
||||
| app.rb:103:5:103:9 | [post] self | app.rb:60:48:62:5 | <captured> self |
|
||||
| app.rb:103:5:103:9 | [post] self | app.rb:74:11:77:5 | <captured> self |
|
||||
| app.rb:103:5:103:9 | [post] self | app.rb:79:11:82:5 | <captured> self |
|
||||
| app.rb:103:5:103:9 | [post] self | app.rb:89:16:92:5 | <captured> self |
|
||||
| app.rb:103:5:103:9 | [post] self | app.rb:94:15:96:5 | <captured> self |
|
||||
| app.rb:103:13:103:22 | [post] self | app.rb:2:22:4:5 | <captured> self |
|
||||
| app.rb:103:13:103:22 | [post] self | app.rb:10:21:13:5 | <captured> self |
|
||||
| app.rb:103:13:103:22 | [post] self | app.rb:15:23:18:5 | <captured> self |
|
||||
| app.rb:103:13:103:22 | [post] self | app.rb:24:26:26:5 | <captured> self |
|
||||
| app.rb:103:13:103:22 | [post] self | app.rb:37:16:42:5 | <captured> self |
|
||||
| app.rb:103:13:103:22 | [post] self | app.rb:44:53:46:5 | <captured> self |
|
||||
| app.rb:103:13:103:22 | [post] self | app.rb:56:32:58:5 | <captured> self |
|
||||
| app.rb:103:13:103:22 | [post] self | app.rb:60:48:62:5 | <captured> self |
|
||||
| app.rb:103:13:103:22 | [post] self | app.rb:74:11:77:5 | <captured> self |
|
||||
| app.rb:103:13:103:22 | [post] self | app.rb:79:11:82:5 | <captured> self |
|
||||
| app.rb:103:13:103:22 | [post] self | app.rb:89:16:92:5 | <captured> self |
|
||||
| app.rb:103:13:103:22 | [post] self | app.rb:94:15:96:5 | <captured> self |
|
||||
30
ruby/ql/test/library-tests/frameworks/sinatra/Sinatra.ql
Normal file
30
ruby/ql/test/library-tests/frameworks/sinatra/Sinatra.ql
Normal file
@@ -0,0 +1,30 @@
|
||||
import ruby
|
||||
import codeql.ruby.frameworks.Sinatra
|
||||
import codeql.ruby.Concepts
|
||||
import codeql.ruby.AST
|
||||
|
||||
query predicate routes(Sinatra::App app, Sinatra::Route route) { route = app.getARoute() }
|
||||
|
||||
query predicate params(Http::Server::RequestInputAccess params) { any() }
|
||||
|
||||
query predicate erbCalls(Sinatra::ErbCall c, ErbFile templateFile) {
|
||||
templateFile = c.getTemplateFile()
|
||||
}
|
||||
|
||||
query predicate erbSyntheticGlobals(Sinatra::ErbLocalsHashSyntheticGlobal g, ErbFile file) {
|
||||
file = g.getErbFile()
|
||||
}
|
||||
|
||||
query predicate filters(Sinatra::Filter filter, string kind) {
|
||||
filter instanceof Sinatra::BeforeFilter and kind = "before"
|
||||
or
|
||||
filter instanceof Sinatra::AfterFilter and kind = "after"
|
||||
}
|
||||
|
||||
query predicate filterPatterns(Sinatra::Filter filter, DataFlow::ExprNode pattern) {
|
||||
pattern = filter.getPattern()
|
||||
}
|
||||
|
||||
query predicate additionalFlowSteps(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(Sinatra::FilterJumpStep s).step(pred, succ)
|
||||
}
|
||||
115
ruby/ql/test/library-tests/frameworks/sinatra/app.rb
Normal file
115
ruby/ql/test/library-tests/frameworks/sinatra/app.rb
Normal file
@@ -0,0 +1,115 @@
|
||||
class MyApp < Sinatra::Base
|
||||
get '/hello/:name' do
|
||||
"Hello #{params['name']}!"
|
||||
end
|
||||
|
||||
get '/goodbye/:name' do |n|
|
||||
"Goodbyte #{n}!"
|
||||
end
|
||||
|
||||
get '/say/*/to/*' do
|
||||
# matches /say/hello/to/world
|
||||
params['splat'] # => ["hello", "world"]
|
||||
end
|
||||
|
||||
get '/download/*.*' do
|
||||
# matches /download/path/to/file.xml
|
||||
params['splat'] # => ["path/to/file", "xml"]
|
||||
end
|
||||
|
||||
get '/download/*.*' do |path, ext|
|
||||
[path, ext] # => ["path/to/file", "xml"]
|
||||
end
|
||||
|
||||
get /\/hello\/([\w]+)/ do
|
||||
"Hello, #{params['captures'].first}!"
|
||||
end
|
||||
|
||||
get %r{/hello/([\w]+)} do |c|
|
||||
# Matches "GET /meta/hello/world", "GET /hello/world/1234" etc.
|
||||
"Hello, #{c}!"
|
||||
end
|
||||
|
||||
get '/posts/:format?' do
|
||||
# matches "GET /posts/" and any extension "GET /posts/json", "GET /posts/xml" etc
|
||||
end
|
||||
|
||||
get '/posts' do
|
||||
# matches "GET /posts?title=foo&author=bar"
|
||||
title = params['title']
|
||||
author = params['author']
|
||||
# uses title and author variables; query is optional to the /posts route
|
||||
end
|
||||
|
||||
get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
|
||||
"You're using Songbird version #{params['agent'][0]}"
|
||||
end
|
||||
|
||||
get '/foo' do
|
||||
# Matches non-songbird browsers
|
||||
end
|
||||
|
||||
get '/', :host_name => /^admin\./ do
|
||||
"Admin Area, Access denied!"
|
||||
end
|
||||
|
||||
get '/', :provides => 'html' do
|
||||
haml :index
|
||||
end
|
||||
|
||||
get '/', :provides => ['rss', 'atom', 'xml'] do
|
||||
builder :feed
|
||||
end
|
||||
|
||||
set(:probability) { |value| condition { rand <= value } }
|
||||
|
||||
get '/win_a_car', :probability => 0.1 do
|
||||
"You won!"
|
||||
end
|
||||
|
||||
get '/win_a_car' do
|
||||
"Sorry, you lost."
|
||||
end
|
||||
|
||||
get '/' do
|
||||
@foo = params["foo"]
|
||||
erb :index, locals: {foo: @foo}
|
||||
end
|
||||
|
||||
get '/' do
|
||||
code = "<%= Time.now %>"
|
||||
erb code
|
||||
end
|
||||
|
||||
before do
|
||||
@note = 'Hi!'
|
||||
request.path_info = '/foo/bar/baz'
|
||||
end
|
||||
|
||||
get '/foo/*' do
|
||||
@note #=> 'Hi!'
|
||||
params['splat'] #=> 'bar/baz'
|
||||
end
|
||||
|
||||
get "/home" do
|
||||
sink @user # $ hasValueFlow=a
|
||||
end
|
||||
|
||||
after do
|
||||
puts response.status
|
||||
end
|
||||
|
||||
before do
|
||||
@user = source "a"
|
||||
end
|
||||
|
||||
before '/protected/*' do
|
||||
authenticate!
|
||||
end
|
||||
|
||||
|
||||
after '/create/:slug' do |slug|
|
||||
session[:last_slug] = slug
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
<%= @foo %>
|
||||
<%= sink foo %>
|
||||
Reference in New Issue
Block a user