Skip to content

Commit f113eaa

Browse files
authored
Merge pull request github#12059 from pwntester/go_twirp_support
[GoLang] Add support for Twirp framework
2 parents 13c7c84 + 1833585 commit f113eaa

File tree

166 files changed

+37865
-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.

166 files changed

+37865
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* Support for the Twirp framework has been added.

go/ql/lib/go.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import semmle.go.frameworks.SQL
5656
import semmle.go.frameworks.Stdlib
5757
import semmle.go.frameworks.SystemCommandExecutors
5858
import semmle.go.frameworks.Testing
59+
import semmle.go.frameworks.Twirp
5960
import semmle.go.frameworks.WebSocket
6061
import semmle.go.frameworks.XNetHtml
6162
import semmle.go.frameworks.XPath
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/** Provides models of commonly used functions and types in the twirp packages. */
2+
3+
import go
4+
private import semmle.go.security.RequestForgeryCustomizations
5+
6+
/** Provides models of commonly used functions and types in the twirp packages. */
7+
module Twirp {
8+
/**
9+
* A *.pb.go file generated by Twirp.
10+
*
11+
* This file contains all the types representing protobuf messages and should have a companion *.twirp.go file.
12+
*/
13+
class ProtobufGeneratedFile extends File {
14+
ProtobufGeneratedFile() {
15+
exists(string prefix, File f |
16+
prefix = this.getBaseName().regexpCapture("^(.*)\\.pb\\.go$", 1) and
17+
this.getParentContainer() = f.getParentContainer() and
18+
f.getBaseName() = prefix + ".twirp.go"
19+
)
20+
}
21+
}
22+
23+
/**
24+
* A *.twirp.go file generated by Twirp.
25+
*
26+
* This file contains all the types representing protobuf services and should have a companion *.pb.go file.
27+
*/
28+
class ServicesGeneratedFile extends File {
29+
ServicesGeneratedFile() {
30+
exists(string prefix, File f |
31+
prefix = this.getBaseName().regexpCapture("^(.*)\\.twirp\\.go$", 1) and
32+
this.getParentContainer() = f.getParentContainer() and
33+
f.getBaseName() = prefix + ".pb.go"
34+
)
35+
}
36+
}
37+
38+
/**
39+
* A type representing a protobuf message.
40+
*/
41+
class ProtobufMessageType extends Type {
42+
ProtobufMessageType() {
43+
exists(TypeEntity te |
44+
te.getType() = this and
45+
te.getDeclaration().getLocation().getFile() instanceof ProtobufGeneratedFile
46+
)
47+
}
48+
}
49+
50+
/**
51+
* An interface type representing a Twirp service.
52+
*/
53+
class ServiceInterfaceType extends InterfaceType {
54+
NamedType namedType;
55+
56+
ServiceInterfaceType() {
57+
exists(TypeEntity te |
58+
te.getType() = namedType and
59+
namedType.getUnderlyingType() = this and
60+
te.getDeclaration().getLocation().getFile() instanceof ServicesGeneratedFile
61+
)
62+
}
63+
64+
/**
65+
* Gets the name of the interface.
66+
*/
67+
override string getName() { result = namedType.getName() }
68+
69+
/**
70+
* Gets the named type on top of this interface type.
71+
*/
72+
NamedType getNamedType() { result = namedType }
73+
}
74+
75+
/**
76+
* A Twirp client.
77+
*/
78+
class ServiceClientType extends NamedType {
79+
ServiceClientType() {
80+
exists(ServiceInterfaceType i, PointerType p, TypeEntity te |
81+
p.implements(i) and
82+
this = p.getBaseType() and
83+
this.getName().regexpMatch("(?i)" + i.getName() + "(protobuf|json)client") and
84+
te.getType() = this and
85+
te.getDeclaration().getLocation().getFile() instanceof ServicesGeneratedFile
86+
)
87+
}
88+
}
89+
90+
/**
91+
* A Twirp server.
92+
*/
93+
class ServiceServerType extends NamedType {
94+
ServiceServerType() {
95+
exists(ServiceInterfaceType i, TypeEntity te |
96+
this.implements(i) and
97+
this.getName().regexpMatch("(?i)" + i.getName() + "server") and
98+
te.getType() = this and
99+
te.getDeclaration().getLocation().getFile() instanceof ServicesGeneratedFile
100+
)
101+
}
102+
}
103+
104+
/**
105+
* A Twirp function to construct a Client.
106+
*/
107+
class ClientConstructor extends Function {
108+
ClientConstructor() {
109+
exists(ServiceClientType c |
110+
this.getName().regexpMatch("(?i)new" + c.getName()) and
111+
this.getParameterType(0) instanceof StringType and
112+
this.getParameterType(1).getName() = "HTTPClient" and
113+
this.getDeclaration().getLocation().getFile() instanceof ServicesGeneratedFile
114+
)
115+
}
116+
}
117+
118+
/**
119+
* A Twirp function to construct a Server.
120+
*
121+
* Its first argument should be an implementation of the service interface.
122+
*/
123+
class ServerConstructor extends Function {
124+
ServerConstructor() {
125+
exists(ServiceServerType c, ServiceInterfaceType i |
126+
this.getName().regexpMatch("(?i)new" + c.getName()) and
127+
this.getParameterType(0) = i.getNamedType() and
128+
this.getDeclaration().getLocation().getFile() instanceof ServicesGeneratedFile
129+
)
130+
}
131+
}
132+
133+
/**
134+
* An SSRF sink for the Client constructor.
135+
*/
136+
class ClientRequestUrlAsSink extends RequestForgery::Sink {
137+
ClientRequestUrlAsSink() {
138+
exists(DataFlow::CallNode call |
139+
call.getArgument(0) = this and
140+
call.getTarget() instanceof ClientConstructor
141+
)
142+
}
143+
144+
override DataFlow::Node getARequest() { result = this }
145+
146+
override string getKind() { result = "URL" }
147+
}
148+
149+
/**
150+
* A service handler.
151+
*/
152+
class ServiceHandler extends Method {
153+
ServiceHandler() {
154+
exists(DataFlow::CallNode call, Type handlerType, ServiceInterfaceType i |
155+
call.getTarget() instanceof ServerConstructor and
156+
call.getArgument(0).getType() = handlerType and
157+
this = handlerType.getMethod(_) and
158+
this.implements(i.getNamedType().getMethod(_))
159+
)
160+
}
161+
}
162+
163+
/**
164+
* A request coming to the service handler.
165+
*/
166+
class Request extends UntrustedFlowSource::Range instanceof DataFlow::ParameterNode {
167+
Request() {
168+
exists(Callable c, ServiceHandler handler | c.asFunction() = handler |
169+
this.isParameterOf(c, 1) and
170+
handler.getParameterType(0).hasQualifiedName("context", "Context") and
171+
this.getType().(PointerType).getBaseType() instanceof ProtobufMessageType
172+
)
173+
}
174+
}
175+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
edges
2+
| client/main.go:16:35:16:78 | &... | client/main.go:16:35:16:78 | &... |
3+
| client/main.go:16:35:16:78 | &... | server/main.go:19:56:19:61 | definition of params |
4+
| rpc/notes/service.twirp.go:473:6:473:13 | definition of typedReq | rpc/notes/service.twirp.go:477:44:477:51 | typedReq |
5+
| rpc/notes/service.twirp.go:477:44:477:51 | typedReq | server/main.go:19:56:19:61 | definition of params |
6+
| rpc/notes/service.twirp.go:554:6:554:13 | definition of typedReq | rpc/notes/service.twirp.go:558:44:558:51 | typedReq |
7+
| rpc/notes/service.twirp.go:558:44:558:51 | typedReq | server/main.go:19:56:19:61 | definition of params |
8+
| server/main.go:19:56:19:61 | definition of params | client/main.go:16:35:16:78 | &... |
9+
| server/main.go:19:56:19:61 | definition of params | rpc/notes/service.twirp.go:473:6:473:13 | definition of typedReq |
10+
| server/main.go:19:56:19:61 | definition of params | rpc/notes/service.twirp.go:554:6:554:13 | definition of typedReq |
11+
| server/main.go:19:56:19:61 | definition of params | server/main.go:19:56:19:61 | definition of params |
12+
| server/main.go:19:56:19:61 | definition of params | server/main.go:19:56:19:61 | definition of params |
13+
| server/main.go:19:56:19:61 | definition of params | server/main.go:30:38:30:48 | selection of Text |
14+
| server/main.go:19:56:19:61 | definition of params | server/main.go:30:38:30:48 | selection of Text |
15+
nodes
16+
| client/main.go:16:35:16:78 | &... | semmle.label | &... |
17+
| rpc/notes/service.twirp.go:473:6:473:13 | definition of typedReq | semmle.label | definition of typedReq |
18+
| rpc/notes/service.twirp.go:477:44:477:51 | typedReq | semmle.label | typedReq |
19+
| rpc/notes/service.twirp.go:554:6:554:13 | definition of typedReq | semmle.label | definition of typedReq |
20+
| rpc/notes/service.twirp.go:558:44:558:51 | typedReq | semmle.label | typedReq |
21+
| server/main.go:19:56:19:61 | definition of params | semmle.label | definition of params |
22+
| server/main.go:19:56:19:61 | definition of params | semmle.label | definition of params |
23+
| server/main.go:30:38:30:48 | selection of Text | semmle.label | selection of Text |
24+
subpaths
25+
#select
26+
| server/main.go:30:38:30:48 | selection of Text | server/main.go:19:56:19:61 | definition of params | server/main.go:30:38:30:48 | selection of Text | The $@ of this request depends on a $@. | server/main.go:30:38:30:48 | selection of Text | URL | server/main.go:19:56:19:61 | definition of params | user-provided value |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE-918/RequestForgery.ql
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)