Skip to content

Commit dd31be4

Browse files
author
Alvaro Muñoz
committed
Support for Twirp framework
1 parent bc36a75 commit dd31be4

File tree

11 files changed

+188
-0
lines changed

11 files changed

+188
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Support for [Twirp framework](https://twitchtv.github.io/twirp/docs/intro.html).

ruby/ql/lib/codeql/ruby/Frameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ private import codeql.ruby.frameworks.ActionDispatch
2727
private import codeql.ruby.frameworks.PosixSpawn
2828
private import codeql.ruby.frameworks.StringFormatters
2929
private import codeql.ruby.frameworks.Json
30+
private import codeql.ruby.frameworks.Twirp
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* Provides classes for modeling the `Twirp` framework.
3+
*/
4+
5+
private import codeql.ruby.DataFlow
6+
private import codeql.ruby.CFG
7+
private import codeql.ruby.ApiGraphs
8+
private import codeql.ruby.AST as Ast
9+
private import codeql.ruby.security.ServerSideRequestForgeryCustomizations
10+
private import codeql.ruby.Concepts
11+
12+
/**
13+
* Provides classes for modeling the `Twirp` framework.
14+
*/
15+
module Twirp {
16+
/**
17+
* A Twirp service instantiation
18+
*/
19+
class ServiceInstantiation extends DataFlow::CallNode {
20+
ServiceInstantiation() {
21+
this =
22+
API::getTopLevelMember("Twirp").getMember("Service").getASubclass*().getAnInstantiation()
23+
}
24+
25+
DataFlow::LocalSourceNode getHandlerSource() { result = this.getArgument(0).getALocalSource() }
26+
27+
API::Node getHandlerClassApiNode() { result.getAnInstantiation() = this.getHandlerSource() }
28+
29+
DataFlow::LocalSourceNode getHandlerClassDataFlowNode() {
30+
result = this.getHandlerClassApiNode().asSource()
31+
}
32+
33+
Ast::Module getHandlerClassAstNode() {
34+
result =
35+
this.getHandlerClassDataFlowNode()
36+
.asExpr()
37+
.(CfgNodes::ExprNodes::ConstantReadAccessCfgNode)
38+
.getExpr()
39+
.getModule()
40+
}
41+
42+
Ast::Method getHandlerMethod() { result = this.getHandlerClassAstNode().getAnInstanceMethod() }
43+
}
44+
45+
/**
46+
* A Twirp client
47+
*/
48+
class ClientInstantiation extends DataFlow::CallNode {
49+
ClientInstantiation() {
50+
this =
51+
API::getTopLevelMember("Twirp").getMember("Client").getASubclass*().getAnInstantiation()
52+
}
53+
}
54+
55+
/** The URL of a Twirp service, considered as a sink. */
56+
class ServiceUrlAsSsrfSink extends ServerSideRequestForgery::Sink {
57+
ServiceUrlAsSsrfSink() { exists(ClientInstantiation c | c.getArgument(0) = this) }
58+
}
59+
60+
/** A parameter that will receive parts of the url when handling an incoming request. */
61+
class UnmarshaledParameter extends Http::Server::RequestInputAccess::Range,
62+
DataFlow::ParameterNode {
63+
UnmarshaledParameter() {
64+
exists(ServiceInstantiation i | i.getHandlerMethod().getParameter(0) = this.asParameter())
65+
}
66+
67+
override string getSourceType() { result = "Twirp Unmarhaled Parameter" }
68+
69+
override Http::Server::RequestInputKind getKind() { result = Http::Server::bodyInputKind() }
70+
}
71+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
source "https://rubygems.org"
2+
3+
gem "rack"
4+
gem "webrick"
5+
gem "twirp"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
private import codeql.ruby.frameworks.Twirp
2+
private import codeql.ruby.DataFlow
3+
4+
query predicate sourceTest(DataFlow::Node s) { s instanceof Twirp::UnmarshaledParameter }
5+
6+
query predicate ssrfSinkTest(DataFlow::Node n) { n instanceof Twirp::ServiceUrlAsSsrfSink }
7+
8+
query predicate serviceInstantiationTest(DataFlow::Node n) {
9+
n instanceof Twirp::ServiceInstantiation
10+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
syntax = "proto3";
2+
package example.hello_world;
3+
4+
5+
service HelloWorld {
6+
rpc Hello(HelloRequest) returns (HelloResponse);
7+
}
8+
9+
message HelloRequest {
10+
string name = 1;
11+
}
12+
13+
message HelloResponse {
14+
string message = 1;
15+
}

ruby/ql/test/library-tests/frameworks/Twirp/hello_world/service_pb.rb

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Code generated by protoc-gen-twirp_ruby 1.10.0, DO NOT EDIT.
2+
require 'twirp'
3+
require_relative 'service_pb.rb'
4+
5+
module Example
6+
module HelloWorld
7+
class HelloWorldService < ::Twirp::Service
8+
package 'example.hello_world'
9+
service 'HelloWorld'
10+
rpc :Hello, HelloRequest, HelloResponse, :ruby_method => :hello
11+
end
12+
13+
class HelloWorldClient < ::Twirp::Client
14+
client_for HelloWorldService
15+
end
16+
end
17+
end
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
require 'rack'
2+
3+
require_relative 'hello_world/service_twirp.rb'
4+
5+
# test: ssrfSink
6+
c = Example::HelloWorld::HelloWorldClient.new("http://localhost:8080/twirp")
7+
8+
resp = c.hello(name: "World")
9+
if resp.error
10+
puts resp.error
11+
else
12+
puts resp.data.message
13+
end
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
require 'rack'
2+
require 'webrick'
3+
4+
require_relative 'hello_world/service_twirp.rb'
5+
6+
class HelloWorldHandler
7+
# test: request
8+
def hello(req, env)
9+
puts ">> Hello #{req.name}"
10+
{message: "Hello #{req.name}"}
11+
end
12+
end
13+
14+
class FakeHelloWorldHandler
15+
# test: !request
16+
def hello(req, env)
17+
puts ">> Hello #{req.name}"
18+
{message: "Hello #{req.name}"}
19+
end
20+
end
21+
22+
handler = HelloWorldHandler.new()
23+
# test: serviceInstantiation
24+
service = Example::HelloWorld::HelloWorldService.new(handler)
25+
26+
path_prefix = "/twirp/" + service.full_name
27+
server = WEBrick::HTTPServer.new(Port: 8080)
28+
server.mount path_prefix, Rack::Handler::WEBrick, service
29+
server.start

0 commit comments

Comments
 (0)