Support for Twirp framework

This commit is contained in:
Alvaro Muñoz
2023-02-03 09:35:22 +01:00
parent bc36a75bde
commit dd31be43e0
11 changed files with 188 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Support for [Twirp framework](https://twitchtv.github.io/twirp/docs/intro.html).

View File

@@ -27,3 +27,4 @@ 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.Twirp

View File

@@ -0,0 +1,71 @@
/**
* Provides classes for modeling the `Twirp` framework.
*/
private import codeql.ruby.DataFlow
private import codeql.ruby.CFG
private import codeql.ruby.ApiGraphs
private import codeql.ruby.AST as Ast
private import codeql.ruby.security.ServerSideRequestForgeryCustomizations
private import codeql.ruby.Concepts
/**
* Provides classes for modeling the `Twirp` framework.
*/
module Twirp {
/**
* A Twirp service instantiation
*/
class ServiceInstantiation extends DataFlow::CallNode {
ServiceInstantiation() {
this =
API::getTopLevelMember("Twirp").getMember("Service").getASubclass*().getAnInstantiation()
}
DataFlow::LocalSourceNode getHandlerSource() { result = this.getArgument(0).getALocalSource() }
API::Node getHandlerClassApiNode() { result.getAnInstantiation() = this.getHandlerSource() }
DataFlow::LocalSourceNode getHandlerClassDataFlowNode() {
result = this.getHandlerClassApiNode().asSource()
}
Ast::Module getHandlerClassAstNode() {
result =
this.getHandlerClassDataFlowNode()
.asExpr()
.(CfgNodes::ExprNodes::ConstantReadAccessCfgNode)
.getExpr()
.getModule()
}
Ast::Method getHandlerMethod() { result = this.getHandlerClassAstNode().getAnInstanceMethod() }
}
/**
* A Twirp client
*/
class ClientInstantiation extends DataFlow::CallNode {
ClientInstantiation() {
this =
API::getTopLevelMember("Twirp").getMember("Client").getASubclass*().getAnInstantiation()
}
}
/** The URL of a Twirp service, considered as a sink. */
class ServiceUrlAsSsrfSink extends ServerSideRequestForgery::Sink {
ServiceUrlAsSsrfSink() { exists(ClientInstantiation c | c.getArgument(0) = this) }
}
/** A parameter that will receive parts of the url when handling an incoming request. */
class UnmarshaledParameter extends Http::Server::RequestInputAccess::Range,
DataFlow::ParameterNode {
UnmarshaledParameter() {
exists(ServiceInstantiation i | i.getHandlerMethod().getParameter(0) = this.asParameter())
}
override string getSourceType() { result = "Twirp Unmarhaled Parameter" }
override Http::Server::RequestInputKind getKind() { result = Http::Server::bodyInputKind() }
}
}

View File

@@ -0,0 +1,5 @@
source "https://rubygems.org"
gem "rack"
gem "webrick"
gem "twirp"

View File

@@ -0,0 +1,10 @@
private import codeql.ruby.frameworks.Twirp
private import codeql.ruby.DataFlow
query predicate sourceTest(DataFlow::Node s) { s instanceof Twirp::UnmarshaledParameter }
query predicate ssrfSinkTest(DataFlow::Node n) { n instanceof Twirp::ServiceUrlAsSsrfSink }
query predicate serviceInstantiationTest(DataFlow::Node n) {
n instanceof Twirp::ServiceInstantiation
}

View File

@@ -0,0 +1,15 @@
syntax = "proto3";
package example.hello_world;
service HelloWorld {
rpc Hello(HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}

View File

@@ -0,0 +1,22 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: hello_world/service.proto
require 'google/protobuf'
Google::Protobuf::DescriptorPool.generated_pool.build do
add_file("hello_world/service.proto", :syntax => :proto3) do
add_message "example.hello_world.HelloRequest" do
optional :name, :string, 1
end
add_message "example.hello_world.HelloResponse" do
optional :message, :string, 1
end
end
end
module Example
module HelloWorld
HelloRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("example.hello_world.HelloRequest").msgclass
HelloResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("example.hello_world.HelloResponse").msgclass
end
end

View File

@@ -0,0 +1,17 @@
# Code generated by protoc-gen-twirp_ruby 1.10.0, DO NOT EDIT.
require 'twirp'
require_relative 'service_pb.rb'
module Example
module HelloWorld
class HelloWorldService < ::Twirp::Service
package 'example.hello_world'
service 'HelloWorld'
rpc :Hello, HelloRequest, HelloResponse, :ruby_method => :hello
end
class HelloWorldClient < ::Twirp::Client
client_for HelloWorldService
end
end
end

View File

@@ -0,0 +1,13 @@
require 'rack'
require_relative 'hello_world/service_twirp.rb'
# test: ssrfSink
c = Example::HelloWorld::HelloWorldClient.new("http://localhost:8080/twirp")
resp = c.hello(name: "World")
if resp.error
puts resp.error
else
puts resp.data.message
end

View File

@@ -0,0 +1,29 @@
require 'rack'
require 'webrick'
require_relative 'hello_world/service_twirp.rb'
class HelloWorldHandler
# test: request
def hello(req, env)
puts ">> Hello #{req.name}"
{message: "Hello #{req.name}"}
end
end
class FakeHelloWorldHandler
# test: !request
def hello(req, env)
puts ">> Hello #{req.name}"
{message: "Hello #{req.name}"}
end
end
handler = HelloWorldHandler.new()
# test: serviceInstantiation
service = Example::HelloWorld::HelloWorldService.new(handler)
path_prefix = "/twirp/" + service.full_name
server = WEBrick::HTTPServer.new(Port: 8080)
server.mount path_prefix, Rack::Handler::WEBrick, service
server.start

View File

@@ -0,0 +1 @@
The query depends on an extensional predicate sinkModel which has not been defined.