Ruby: Add classes for detecting user input from graphql-ruby

This commit is contained in:
Jeff Gran
2021-11-13 13:42:17 -07:00
parent 1912c56f82
commit 47697f59c1
18 changed files with 678 additions and 0 deletions

View File

@@ -6,6 +6,7 @@ private import codeql.ruby.frameworks.ActionController
private import codeql.ruby.frameworks.ActiveRecord
private import codeql.ruby.frameworks.ActiveStorage
private import codeql.ruby.frameworks.ActionView
private import codeql.ruby.frameworks.GraphQL
private import codeql.ruby.frameworks.Rails
private import codeql.ruby.frameworks.StandardLibrary
private import codeql.ruby.frameworks.Files

View File

@@ -0,0 +1,422 @@
private import codeql.ruby.AST
private import codeql.ruby.Concepts
private import codeql.ruby.controlflow.CfgNodes
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.RemoteFlowSources
private import codeql.ruby.ast.internal.Module
private import codeql.ruby.ApiGraphs
private class GraphqlRelayClassicMutationAccess extends ConstantReadAccess {
//GraphQL::Schema::RelayClassicMutation
GraphqlRelayClassicMutationAccess() {
this =
API::getTopLevelMember("GraphQL")
.getMember("Schema")
.getMember("RelayClassicMutation")
.getAUse()
.asExpr()
.getExpr()
}
}
private class GraphqlSchemaResolverAccess extends ConstantReadAccess {
//GraphQL::Schema::Resolver
GraphqlSchemaResolverAccess() {
this =
API::getTopLevelMember("GraphQL")
.getMember("Schema")
.getMember("Resolver")
.getAUse()
.asExpr()
.getExpr()
}
}
private class GraphqlSchemaObjectAccess extends ConstantReadAccess {
//GraphQL::Schema::Object
GraphqlSchemaObjectAccess() {
this =
API::getTopLevelMember("GraphQL")
.getMember("Schema")
.getMember("Object")
.getAUse()
.asExpr()
.getExpr()
}
}
/**
* A `ClassDeclaration` for a class that extends `GraphQL::Schema::RelayClassicMutation`.
* For example,
*
* ```rb
* module Mutations
* class BaseMutation < GraphQL::Schema::RelayClassicMutation
* argument_class Types::BaseArgument
* field_class Types::BaseField
* input_object_class Types::BaseInputObject
* object_class Types::BaseObject
* end
* end
*
* module Mutation
* class MyMutation < BaseMutation
* argument :something_id, ID, required: false
* field :success, Boolean, null: false
*
* def resolve(something_id:)
* # call your application logic here...
* end
* end
* end
* ```
*/
private class GraphqlRelayClassicMutationClass extends ClassDeclaration {
GraphqlRelayClassicMutationClass() {
// class BaseMutation < GraphQL::Schema::RelayClassicMutation
this.getSuperclassExpr() instanceof GraphqlRelayClassicMutationAccess
or
// class MyMutation < BaseMutation
exists(GraphqlRelayClassicMutationClass other |
other.getModule() = resolveConstantReadAccess(this.getSuperclassExpr())
)
}
}
/**
* A `ClassDeclaration` for a class that extends `GraphQL::Schema::Resolver`.
* For example,
*
* ```rb
* module Resolvers
* class Base < GraphQL::Schema::Resolver
* argument_class Arguments::Base
* end
* end
*
* module Resolvers
* class RecommendedItems < Resolvers::Base
* type [Types::Item], null: false
* argument :order_by, Types::ItemOrder, required: false
*
* def resolve(order_by: )
* # call your application logic here...
* end
* end
* end
* ```
*/
private class GraphqlSchemaResolverClass extends ClassDeclaration {
GraphqlSchemaResolverClass() {
// class BaseResolver < GraphQL::Schema::Resolver
this.getSuperclassExpr() instanceof GraphqlSchemaResolverAccess
or
// class MyResolver < BaseResolver
exists(GraphqlSchemaResolverClass other |
other.getModule() = resolveConstantReadAccess(this.getSuperclassExpr())
)
}
}
/**
* A `ClassDeclaration` for a class that extends `GraphQL::Schema::Object`.
* For example,
*
* ```rb
* class BaseObject < GraphQL::Schema::Object
* field_class BaseField
* end
*
* class Musician < BaseObject
* field :favorite_key, Key
* end
* ```
*/
class GraphqlSchemaObjectClass extends ClassDeclaration {
GraphqlSchemaObjectClass() {
// class BaseObject < GraphQL::Schema::Object
this.getSuperclassExpr() instanceof GraphqlSchemaObjectAccess
or
// class MyObject < BaseObject
exists(GraphqlSchemaObjectClass other |
other.getModule() = resolveConstantReadAccess(this.getSuperclassExpr())
)
}
/** Gets a `GraphqlFieldDefinitionMethodCall` called in this class. */
GraphqlFieldDefinitionMethodCall getAFieldDefinitionMethodCall() {
result.getReceiverClass() = this
}
}
/**
* A `ClassDeclaration` for a class that extends either
* `GraphQL::Schema::RelayClassicMutation` or
* `GraphQL::Schema::Resolver`.
*
* Both of these classes have an overrideable `resolve` instance
* method which can receive user input in order to resolve a query or mutation.
*/
private class GraphqlResolvableClass extends ClassDeclaration {
GraphqlResolvableClass() {
this instanceof GraphqlRelayClassicMutationClass or
this instanceof GraphqlSchemaResolverClass
}
}
/**
* A `resolve` instance method on a sub-class of either
* `GraphQL::Schema::RelayClassicMutation` or
* `GraphQL::Schema::Resolver`.
*
* This `resolve` method is essentially an HTTP request handler.
* The user input data comes in through a GraphQL query, is parsed by the GraphQL
* library, and this method handles the request. Then the result is serialized
* into a GraphQL response on the way out.
*
* For example:
*
* ```rb
* module Mutation
* class NameAnInstrument < BaseMutationn
* argument :instrument_uuid, Types::Uuid,
* required: true,
* loads: ::Instrument,
* as: :instrument,
* argument :name, String, required: true
*
* def load_instrument(uuid)
* ::Instrument.find_by(uuid: uuid)
* end
*
* # GraphqlResolveMethod
* def resolve(instrument:, name:)
* instrument.set_name(name)
* end
* end
* end
* ```
*/
class GraphqlResolveMethod extends Method, HTTP::Server::RequestHandler::Range {
private GraphqlResolvableClass resolvableClass;
GraphqlResolveMethod() { this = resolvableClass.getMethod("resolve") }
override Parameter getARoutedParameter() { result = this.getAParameter() }
override string getFramework() { result = "GraphQL" }
/** Gets the mutation class containing this method. */
GraphqlResolvableClass getMutationClass() { result = resolvableClass }
}
/**
* A `load_*` method on a sub-class of either
* `GraphQL::Schema::RelayClassicMutation` or
* `GraphQL::Schema::Resolver`.
*
* This method takes user input (some kind of ID or specifier) and is intended
* to resolve the domain object using that ID.
*
* For example:
*
* ```rb
* module Mutation
* class NameAnInstrument < BaseMutationn
* argument :instrument_uuid, Types::Uuid,
* required: true,
* loads: ::Instrument,
* as: :instrument,
* argument :name, String, required: true
*
* # GraphqlLoadMethod
* def load_instrument(uuid)
* ::Instrument.find_by(uuid: uuid)
* end
*
* def resolve(instrument:, name:)
* instrument.set_name(name)
* end
* end
* end
* ```
*/
class GraphqlLoadMethod extends Method, HTTP::Server::RequestHandler::Range {
private GraphqlResolvableClass resolvableClass;
GraphqlLoadMethod() {
this.getEnclosingModule() = resolvableClass and
this.getName().regexpMatch("^load_.*")
}
override Parameter getARoutedParameter() { result = this.getAParameter() }
override string getFramework() { result = "GraphQL" }
/** Gets the mutation class containing this method. */
GraphqlResolvableClass getMutationClass() { result = resolvableClass }
}
/**
* A `MethodCall` that represents calling a class method on a
* a sub-class of `GraphQL::Schema::Object`
*/
private class GraphqlSchemaObjectClassMethodCall extends MethodCall {
private GraphqlSchemaObjectClass recvCls;
GraphqlSchemaObjectClassMethodCall() {
// e.g. Foo.some_method(...)
recvCls.getModule() = resolveConstantReadAccess(this.getReceiver())
or
// e.g. self.some_method(...) within a graphql Object or Interface
this.getReceiver() instanceof Self and
this.getEnclosingModule() = recvCls
}
/** Gets the `GraphqlSchemaObjectClass` representing the receiver of this method. */
GraphqlSchemaObjectClass getReceiverClass() { result = recvCls }
}
/**
* A `MethodCall` that represents calling the class method `field` on a GraphQL
* object.
*
* For example:
*
* ```rb
* class Types::User < GraphQL::Schema::Object
* # GraphqlFieldDefinitionMethodCall
* field :email, String
* end
* ```
*
* See also: https://graphql-ruby.org/fields/introduction.html
*/
class GraphqlFieldDefinitionMethodCall extends GraphqlSchemaObjectClassMethodCall {
GraphqlFieldDefinitionMethodCall() { this.getMethodName() = "field" }
/** Gets the name of this GraphQL field. */
string getFieldName() { result = this.getArgument(0).getValueText() }
}
/**
* A `MethodCall` that represents calling the class method `argument` inside the
* block for a `field` definition on a GraphQL object.
*
* For example:
*
* ```rb
* class Types::User < GraphQL::Schema::Object
* field :email, String
* field :friends, [Types::User] do
* # GraphqlFieldArgumentDefinitionMethodCall
* argument :starts_with, String, "Show only friends matching the given prefix"
* end
* end
* ```
*
* See Also: https://graphql-ruby.org/fields/arguments
*/
private class GraphqlFieldArgumentDefinitionMethodCall extends GraphqlSchemaObjectClassMethodCall {
private GraphqlFieldDefinitionMethodCall fieldDefinition;
GraphqlFieldArgumentDefinitionMethodCall() {
this.getMethodName() = "argument" and
fieldDefinition.getBlock() = this.getEnclosingCallable()
}
/** Gets the method call that defines the GraphQL field this argument is for */
GraphqlFieldDefinitionMethodCall getFieldDefinition() { result = fieldDefinition }
/** Gets the name of the field which this is an argument for */
string getFieldName() { result = this.getFieldDefinition().getFieldName() }
/** Gets the name of the argument (i.e. the first argument to this `argument` method call) */
string getArgumentName() { result = this.getArgument(0).(SymbolLiteral).getValueText() }
}
/**
* A `Method` which represents an instance method which is the resolver method for a
* GraphQL `field`.
*
* For example:
*
* ```rb
* class Types::User < GraphQL::Schema::Object
* field :email, String
* field :friends, [Types::User] do
* argument :starts_with, String, "Show only friends matching the given prefix"
* end
*
* # GraphqlFieldResolutionMethod
* def friends(starts_with:)
* object.friends.where("name like #{starts_with}")
* end
* end
* ```
*
* or:
*
* ```rb
* class Types::User < GraphQL::Schema::Object
* field :email, String
* field :friends, [Types::User] do
* argument :starts_with, String, "Show only friends matching the given prefix",
* resolver_method: :my_custom_method, extras: [:graphql_name]
* end
*
* # GraphqlFieldResolutionMethod
* def my_custom_method(**args)
* puts args[:graphql_name] # for debugging
* object.friends.where("name like #{args[:starts_with]}")
* end
* end
* ```
*/
class GraphqlFieldResolutionMethod extends Method, HTTP::Server::RequestHandler::Range {
private GraphqlSchemaObjectClass schemaObjectClass;
GraphqlFieldResolutionMethod() {
this.getEnclosingModule() = schemaObjectClass and
exists(GraphqlFieldDefinitionMethodCall defn |
// field :foo, resolver_method: :custom_method
// def custom_method(...)
defn.getKeywordArgument("resolver_method").(SymbolLiteral).getValueText() = this.getName()
or
// field :foo
// def foo(...)
not exists(defn.getKeywordArgument("resolver_method").(SymbolLiteral)) and
defn.getFieldName() = this.getName()
)
}
/** Gets the method call which is the definition of the field corresponding to this resolver method. */
GraphqlFieldDefinitionMethodCall getDefinition() {
result.getKeywordArgument("resolver_method").(SymbolLiteral).getValueText() = this.getName()
or
not exists(result.getKeywordArgument("resolver_method").(SymbolLiteral)) and
result.getFieldName() = this.getName()
}
// check for a named argument the same name as a defined argument for this field
override Parameter getARoutedParameter() {
result = this.getAParameter() and
exists(GraphqlFieldArgumentDefinitionMethodCall argDefn |
argDefn.getEnclosingCallable() = this.getDefinition().getBlock() and
(
result.(KeywordParameter).hasName(argDefn.getArgumentName())
or
// TODO this will cause false positives because now *anything* in the **args
// param will be flagged as as RoutedParameter/RemoteFlowSource, but really
// only the hash keys corresponding to the defined arguments are user input
// others could be things defined in the `:extras` keyword argument to the `argument`
result instanceof HashSplatParameter // often you see `def field(**args)`
)
)
}
override string getFramework() { result = "GraphQL" }
/** Gets the class containing this method. */
GraphqlSchemaObjectClass getGraphqlClass() { result = schemaObjectClass }
}

View File

@@ -0,0 +1,46 @@
graphqlSchemaObjectClass
| app/graphql/types/base_object.rb:2:3:4:5 | BaseObject |
| app/graphql/types/mutation_type.rb:2:3:4:5 | MutationType |
| app/graphql/types/query_type.rb:2:3:45:5 | QueryType |
graphqlSchemaObjectFieldDefinition
| app/graphql/types/mutation_type.rb:2:3:4:5 | MutationType | app/graphql/types/mutation_type.rb:3:5:3:44 | call to field |
| app/graphql/types/query_type.rb:2:3:45:5 | QueryType | app/graphql/types/query_type.rb:3:5:5:40 | call to field |
| app/graphql/types/query_type.rb:2:3:45:5 | QueryType | app/graphql/types/query_type.rb:7:5:9:7 | call to field |
| app/graphql/types/query_type.rb:2:3:45:5 | QueryType | app/graphql/types/query_type.rb:15:5:17:7 | call to field |
| app/graphql/types/query_type.rb:2:3:45:5 | QueryType | app/graphql/types/query_type.rb:24:5:26:7 | call to field |
| app/graphql/types/query_type.rb:2:3:45:5 | QueryType | app/graphql/types/query_type.rb:32:5:35:7 | call to field |
graphqlResolveMethod
| app/graphql/mutations/dummy.rb:9:5:12:7 | resolve |
| app/graphql/resolvers/dummy_resolver.rb:10:5:13:7 | resolve |
graphqlResolveMethodRoutedParameter
| app/graphql/mutations/dummy.rb:9:5:12:7 | resolve | app/graphql/mutations/dummy.rb:9:17:9:25 | something |
| app/graphql/resolvers/dummy_resolver.rb:10:5:13:7 | resolve | app/graphql/resolvers/dummy_resolver.rb:10:17:10:25 | something |
graphqlLoadMethod
| app/graphql/mutations/dummy.rb:5:5:7:7 | load_something |
| app/graphql/resolvers/dummy_resolver.rb:6:5:8:7 | load_something |
graphqlLoadMethodRoutedParameter
| app/graphql/mutations/dummy.rb:5:5:7:7 | load_something | app/graphql/mutations/dummy.rb:5:24:5:25 | id |
| app/graphql/resolvers/dummy_resolver.rb:6:5:8:7 | load_something | app/graphql/resolvers/dummy_resolver.rb:6:24:6:25 | id |
graphqlFieldDefinitionMethodCall
| app/graphql/types/mutation_type.rb:3:5:3:44 | call to field |
| app/graphql/types/query_type.rb:3:5:5:40 | call to field |
| app/graphql/types/query_type.rb:7:5:9:7 | call to field |
| app/graphql/types/query_type.rb:15:5:17:7 | call to field |
| app/graphql/types/query_type.rb:24:5:26:7 | call to field |
| app/graphql/types/query_type.rb:32:5:35:7 | call to field |
graphqlFieldResolutionMethod
| app/graphql/types/query_type.rb:10:5:13:7 | with_arg |
| app/graphql/types/query_type.rb:18:5:22:7 | custom_method |
| app/graphql/types/query_type.rb:27:5:30:7 | with_splat |
| app/graphql/types/query_type.rb:36:5:40:7 | with_splat_and_named_arg |
graphqlFieldResolutionRoutedParameter
| app/graphql/types/query_type.rb:10:5:13:7 | with_arg | app/graphql/types/query_type.rb:10:18:10:23 | number |
| app/graphql/types/query_type.rb:18:5:22:7 | custom_method | app/graphql/types/query_type.rb:18:23:18:33 | blah_number |
| app/graphql/types/query_type.rb:27:5:30:7 | with_splat | app/graphql/types/query_type.rb:27:20:27:25 | **args |
| app/graphql/types/query_type.rb:36:5:40:7 | with_splat_and_named_arg | app/graphql/types/query_type.rb:36:34:36:37 | arg1 |
| app/graphql/types/query_type.rb:36:5:40:7 | with_splat_and_named_arg | app/graphql/types/query_type.rb:36:41:36:46 | **rest |
graphqlFieldResolutionDefinition
| app/graphql/types/query_type.rb:10:5:13:7 | with_arg | app/graphql/types/query_type.rb:7:5:9:7 | call to field |
| app/graphql/types/query_type.rb:18:5:22:7 | custom_method | app/graphql/types/query_type.rb:15:5:17:7 | call to field |
| app/graphql/types/query_type.rb:27:5:30:7 | with_splat | app/graphql/types/query_type.rb:24:5:26:7 | call to field |
| app/graphql/types/query_type.rb:36:5:40:7 | with_splat_and_named_arg | app/graphql/types/query_type.rb:32:5:35:7 | call to field |

View File

@@ -0,0 +1,36 @@
private import codeql.ruby.frameworks.GraphQL
private import codeql.ruby.AST
query predicate graphqlSchemaObjectClass(GraphqlSchemaObjectClass cls) { any() }
query predicate graphqlSchemaObjectFieldDefinition(
GraphqlSchemaObjectClass cls, GraphqlFieldDefinitionMethodCall meth
) {
cls.getAFieldDefinitionMethodCall() = meth
}
query predicate graphqlResolveMethod(GraphqlResolveMethod meth) { any() }
query predicate graphqlResolveMethodRoutedParameter(GraphqlResolveMethod meth, Parameter p) {
meth.getARoutedParameter() = p
}
query predicate graphqlLoadMethod(GraphqlLoadMethod meth) { any() }
query predicate graphqlLoadMethodRoutedParameter(GraphqlLoadMethod meth, Parameter p) {
meth.getARoutedParameter() = p
}
query predicate graphqlFieldDefinitionMethodCall(GraphqlFieldDefinitionMethodCall cls) { any() }
query predicate graphqlFieldResolutionMethod(GraphqlFieldResolutionMethod cls) { any() }
query predicate graphqlFieldResolutionRoutedParameter(GraphqlFieldResolutionMethod meth, Parameter p) {
meth.getARoutedParameter() = p
}
query predicate graphqlFieldResolutionDefinition(
GraphqlFieldResolutionMethod meth, GraphqlFieldDefinitionMethodCall def
) {
meth.getDefinition() = def
}

View File

@@ -19,6 +19,15 @@ kernelSystemCallExecutions
| CommandExecution.rb:19:1:19:59 | call to system |
| CommandExecution.rb:20:1:20:62 | call to system |
| CommandExecution.rb:21:1:21:72 | call to system |
| app/graphql/mutations/dummy.rb:10:7:10:33 | call to system |
| app/graphql/resolvers/dummy_resolver.rb:11:7:11:33 | call to system |
| app/graphql/types/query_type.rb:11:7:11:30 | call to system |
| app/graphql/types/query_type.rb:19:7:19:35 | call to system |
| app/graphql/types/query_type.rb:20:7:20:30 | call to system |
| app/graphql/types/query_type.rb:28:7:28:40 | call to system |
| app/graphql/types/query_type.rb:37:7:37:28 | call to system |
| app/graphql/types/query_type.rb:38:7:38:35 | call to system |
| app/graphql/types/query_type.rb:43:7:43:27 | call to system |
kernelExecCallExecutions
| CommandExecution.rb:23:1:23:16 | call to exec |
| CommandExecution.rb:24:1:24:19 | call to exec |

View File

@@ -0,0 +1,8 @@
module Mutations
class BaseMutation < GraphQL::Schema::RelayClassicMutation
argument_class Types::BaseArgument
field_class Types::BaseField
input_object_class Types::BaseInputObject
object_class Types::BaseObject
end
end

View File

@@ -0,0 +1,14 @@
module Mutations
class Dummy < BaseMutation
argument :something_id, ID, required: false
def load_something(id)
"Something number #{id}"
end
def resolve(something:)
system("echo #{something}")
{ success: true }
end
end
end

View File

@@ -0,0 +1,4 @@
module Resolvers
class Base < GraphQL::Schema::Resolver
end
end

View File

@@ -0,0 +1,15 @@
module Resolvers
class DummyResolver < Resolvers::Base
type String, null: false
argument :something_id, ID, required: true
def load_something(id)
"Something number #{id}"
end
def resolve(something:)
system("echo #{something}")
"true"
end
end
end

View File

@@ -0,0 +1,4 @@
module Types
class BaseArgument < GraphQL::Schema::Argument
end
end

View File

@@ -0,0 +1,5 @@
module Types
class BaseField < GraphQL::Schema::Field
argument_class Types::BaseArgument
end
end

View File

@@ -0,0 +1,5 @@
module Types
class BaseInputObject < GraphQL::Schema::InputObject
argument_class Types::BaseArgument
end
end

View File

@@ -0,0 +1,7 @@
module Types
module BaseInterface
include GraphQL::Schema::Interface
field_class Types::BaseField
end
end

View File

@@ -0,0 +1,5 @@
module Types
class BaseObject < GraphQL::Schema::Object
field_class Types::BaseField
end
end

View File

@@ -0,0 +1,5 @@
module Types
class MutationType < Types::BaseObject
field :dummy, mutation: Mutations::Dummy
end
end

View File

@@ -0,0 +1,46 @@
module Types
class QueryType < Types::BaseObject
field :test_field, String, null: false,
description: "An example field added by the generator",
resolver: Resolvers::DummyResolver
field :with_arg, String, null: false, description: "A field with an argument" do
argument :number, Int, "A number", required: true
end
def with_arg(number:)
system("echo #{number}")
number.to_s
end
field :with_method, String, null: false, description: "A field with a custom resolver method", resolver_method: :custom_method do
argument :blah_number, Int, "A number", required: true
end
def custom_method(blah_number:, number: nil)
system("echo #{blah_number}")
system("echo #{number}")
blah_number.to_s
end
field :with_splat, String, null: false, description: "A field with a double-splatted argument" do
argument :something, Int, "A number", required: true
end
def with_splat(**args)
system("echo #{args[:something]}")
args[:something].to_s
end
field :with_splat_and_named_arg, String, null: false, description: "A field with two named arguments, where the method captures the second via a hash splat param" do
argument :arg1, Int, "A number", required: true
argument :arg2, Int, "Another number", required: true
end
def with_splat_and_named_arg(arg1:, **rest)
system("echo #{arg1}")
system("echo #{rest[:arg2]}")
arg1.to_s
end
def foo(arg)
system("echo #{arg}")
end
end
end

View File

@@ -10,6 +10,8 @@ edges
| CommandInjection.rb:6:15:6:26 | ...[...] : | CommandInjection.rb:34:39:34:51 | "grep #{...}" |
| CommandInjection.rb:46:15:46:20 | call to params : | CommandInjection.rb:46:15:46:26 | ...[...] : |
| CommandInjection.rb:46:15:46:26 | ...[...] : | CommandInjection.rb:50:24:50:36 | "echo #{...}" |
| CommandInjection.rb:64:18:64:23 | number : | CommandInjection.rb:65:14:65:29 | "echo #{...}" |
| CommandInjection.rb:72:23:72:33 | blah_number : | CommandInjection.rb:73:14:73:34 | "echo #{...}" |
nodes
| CommandInjection.rb:6:15:6:20 | call to params : | semmle.label | call to params : |
| CommandInjection.rb:6:15:6:26 | ...[...] : | semmle.label | ...[...] : |
@@ -24,6 +26,10 @@ nodes
| CommandInjection.rb:46:15:46:20 | call to params : | semmle.label | call to params : |
| CommandInjection.rb:46:15:46:26 | ...[...] : | semmle.label | ...[...] : |
| CommandInjection.rb:50:24:50:36 | "echo #{...}" | semmle.label | "echo #{...}" |
| CommandInjection.rb:64:18:64:23 | number : | semmle.label | number : |
| CommandInjection.rb:65:14:65:29 | "echo #{...}" | semmle.label | "echo #{...}" |
| CommandInjection.rb:72:23:72:33 | blah_number : | semmle.label | blah_number : |
| CommandInjection.rb:73:14:73:34 | "echo #{...}" | semmle.label | "echo #{...}" |
subpaths
#select
| CommandInjection.rb:7:10:7:15 | #{...} | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:7:10:7:15 | #{...} | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value |
@@ -35,3 +41,5 @@ subpaths
| CommandInjection.rb:33:24:33:36 | "echo #{...}" | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:33:24:33:36 | "echo #{...}" | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value |
| CommandInjection.rb:34:39:34:51 | "grep #{...}" | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:34:39:34:51 | "grep #{...}" | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value |
| CommandInjection.rb:50:24:50:36 | "echo #{...}" | CommandInjection.rb:46:15:46:20 | call to params : | CommandInjection.rb:50:24:50:36 | "echo #{...}" | This command depends on $@. | CommandInjection.rb:46:15:46:20 | call to params | a user-provided value |
| CommandInjection.rb:65:14:65:29 | "echo #{...}" | CommandInjection.rb:64:18:64:23 | number : | CommandInjection.rb:65:14:65:29 | "echo #{...}" | This command depends on $@. | CommandInjection.rb:64:18:64:23 | number | a user-provided value |
| CommandInjection.rb:73:14:73:34 | "echo #{...}" | CommandInjection.rb:72:23:72:33 | blah_number : | CommandInjection.rb:73:14:73:34 | "echo #{...}" | This command depends on $@. | CommandInjection.rb:72:23:72:33 | blah_number | a user-provided value |

View File

@@ -50,3 +50,41 @@ EOF
Open3.capture2("echo #{cmd}")
end
end
module Types
class BaseObject < GraphQL::Schema::Object; end
class QueryType < BaseObject
field :test_field, String, null: false,
description: "An example field added by the generator",
resolver: Resolvers::DummyResolver
field :with_arg, String, null: false, description: "A field with an argument" do
argument :number, Int, "A number", required: true
end
def with_arg(number:)
system("echo #{number}")
number.to_s
end
field :with_method, String, null: false, description: "A field with a custom resolver method", resolver_method: :custom_method do
argument :blah_number, Int, "A number", required: true
end
def custom_method(blah_number:, number: nil)
system("echo #{blah_number}")
system("echo #{number}") # OK, number: is not an `argument` for this field
blah_number.to_s
end
field :with_splat, String, null: false, description: "A field with a double-splatted argument" do
argument :something, Int, "A number", required: true
end
def with_splat(**args)
system("echo #{args[:something]}")
args[:something].to_s
end
def foo(arg)
system("echo #{arg}") # OK, this is just a random method, not a resolver method
end
end
end