Skip to content

Commit afa6b1c

Browse files
author
Alvaro Muñoz
committed
Initial support for Twirp framework
1 parent bc36a75 commit afa6b1c

File tree

162 files changed

+37797
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

162 files changed

+37797
-0
lines changed
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import go
2+
import semmle.go.security.RequestForgery
3+
4+
module Twirp {
5+
/**
6+
* A *.pb.go file generated by Twirp.
7+
* This file will all the types representing protobuf messages and should have a companion *.twirp.go file.
8+
*/
9+
class ProtobufGeneratedFile extends File {
10+
ProtobufGeneratedFile() {
11+
this.getBaseName().matches("%.pb.go") and
12+
exists(File f |
13+
this.getParentContainer() = f.getParentContainer() and
14+
this.getBaseName().splitAt(".", 0) = f.getBaseName().splitAt(".", 0) and
15+
f.getBaseName().matches("%.twirp.go")
16+
)
17+
}
18+
}
19+
20+
/**
21+
* A *.twirp.go file generated by Twirp.
22+
* This file contains all the types representing protobuf services and should have a companion *.pb.go file.
23+
*/
24+
class ServicesGeneratedFile extends File {
25+
ServicesGeneratedFile() {
26+
this.getBaseName().matches("%.twirp.go") and
27+
exists(File f |
28+
this.getParentContainer() = f.getParentContainer() and
29+
this.getBaseName().splitAt(".", 0) = f.getBaseName().splitAt(".", 0) and
30+
f.getBaseName().matches("%.pb.go")
31+
)
32+
}
33+
}
34+
35+
/**
36+
* A type representing a protobuf message.
37+
*/
38+
class ProtobufMessage extends Type {
39+
ProtobufMessage() {
40+
exists(TypeEntity te |
41+
te.getType() = this and
42+
te.getDeclaration().getLocation().getFile() instanceof ProtobufGeneratedFile
43+
)
44+
}
45+
}
46+
47+
/**
48+
* An interface type representing a Twirp service.
49+
*/
50+
class ServiceInterface extends NamedType {
51+
ServiceInterface() {
52+
exists(TypeEntity te |
53+
te.getType() = this and
54+
// To match an Interface type we need to use a NamedType whose getUnderlying type is an InterfaceType
55+
this.getUnderlyingType() instanceof InterfaceType and
56+
te.getDeclaration().getLocation().getFile() instanceof ServicesGeneratedFile
57+
)
58+
}
59+
60+
InterfaceType getInterfaceType() { result = this.getUnderlyingType() }
61+
}
62+
63+
/**
64+
* A Twirp client
65+
*/
66+
class ServiceClient extends NamedType {
67+
PointerType pointerType;
68+
69+
ServiceClient() {
70+
exists(ServiceInterface i |
71+
pointerType.implements(i.getInterfaceType()) and
72+
this = pointerType.getBaseType() and
73+
this.getName().toLowerCase() = i.getName().toLowerCase() + ["protobuf", "json"] + "client"
74+
)
75+
}
76+
}
77+
78+
/**
79+
* A Twirp server
80+
*/
81+
class ServiceServer extends NamedType {
82+
ServiceServer() {
83+
exists(ServiceInterface i |
84+
this.implements(i.getInterfaceType()) and
85+
this.getName().toLowerCase() = i.getName().toLowerCase() + "server"
86+
)
87+
}
88+
}
89+
90+
/**
91+
* Twirp function to construct a Client
92+
*/
93+
class ClientConstructor extends Function {
94+
ClientConstructor() {
95+
exists(ServiceClient c |
96+
this.getName().toLowerCase() = "new" + c.getName().toLowerCase() and
97+
this.getParameter(0).getType().getName() = "string" and
98+
this.getParameter(1).getType().getName() = "HTTPClient"
99+
)
100+
}
101+
}
102+
103+
/**
104+
* Twirp function to construct a Server
105+
* Its first argument should be an implementation of the service interface
106+
*/
107+
class ServerConstructor extends Function {
108+
ServerConstructor() {
109+
exists(ServiceServer c |
110+
this.getName().toLowerCase() = "new" + c.getName().toLowerCase() and
111+
this.getParameter(0).getType() instanceof ServiceInterface
112+
)
113+
}
114+
}
115+
116+
/**
117+
* SSRF sink for the Client constructor
118+
*/
119+
class ClientRequestUrlAsSink extends RequestForgery::Sink {
120+
ClientRequestUrlAsSink() {
121+
exists(DataFlow::CallNode call |
122+
call.getArgument(0) = this and
123+
call.getTarget() instanceof ClientConstructor
124+
)
125+
}
126+
127+
override DataFlow::Node getARequest() { none() }
128+
129+
override string getKind() { result = "URL" }
130+
}
131+
132+
/**
133+
* A service handler
134+
*/
135+
class ServiceHandler extends Method {
136+
Method m;
137+
138+
ServiceHandler() {
139+
exists(DataFlow::CallNode call, Type handlerType, ServiceInterface i |
140+
call.getTarget() instanceof ServerConstructor and
141+
call.getArgument(0).getType() = handlerType and
142+
handlerType.implements(i.getInterfaceType()) and
143+
this = handlerType.getMethod(_) and
144+
this.implements(m) and
145+
i.getMethod(_) = m
146+
)
147+
}
148+
}
149+
150+
/**
151+
* A request comming to the service handler
152+
*/
153+
class Request extends UntrustedFlowSource::Range, DataFlow::ParameterNode {
154+
ServiceHandler handler;
155+
156+
Request() {
157+
handler.getParameter(0).getType().hasQualifiedName("context", "Context") and
158+
handler.getParameter(_) = this.asParameter() and
159+
this.getType().(PointerType).getBaseType() instanceof ProtobufMessage
160+
}
161+
162+
override predicate isParameterOf(Callable c, int i) {
163+
c.asFunction() = handler and
164+
i = 1
165+
}
166+
}
167+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"log"
6+
"net/http"
7+
8+
"github.com/pwntester/go-twirp-rpc-example/rpc/notes"
9+
)
10+
11+
func main() {
12+
client := notes.NewNotesServiceProtobufClient("http://localhost:8000", &http.Client{}) // test: ssrfSink
13+
14+
ctx := context.Background()
15+
16+
_, err := client.CreateNote(ctx, &notes.CreateNoteParams{Text: "Hello World"})
17+
if err != nil {
18+
log.Fatal(err)
19+
}
20+
allNotes, err := client.GetAllNotes(ctx, &notes.GetAllNotesParams{})
21+
if err != nil {
22+
log.Fatal(err)
23+
}
24+
25+
for _, note := range allNotes.Notes {
26+
log.Println(note)
27+
}
28+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module github.com/pwntester/go-twirp-rpc-example
2+
3+
go 1.19
4+
5+
require (
6+
github.com/twitchtv/twirp v8.1.3+incompatible
7+
google.golang.org/protobuf v1.28.1
8+
)
9+
10+
require github.com/pkg/errors v0.9.1 // indirect

0 commit comments

Comments
 (0)