webwebhookhook
I made a service to convert webhooks into webhooks.
We are given a Spring application written in Kotlin that allows us to create webhooks:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package tf.irisc.chal.webwebhookhook
import java.net.URI
import java.net.URL
class StateType(
hook: String,
var template: String,
var response: String
) {
var hook: URL = URI.create(hook).toURL()
}
object State {
var arr = ArrayList<StateType>()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package tf.irisc.chal.webwebhookhook.controller
import org.springframework.http.MediaType
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.*
import tf.irisc.chal.webwebhookhook.State
import tf.irisc.chal.webwebhookhook.StateType
import java.net.HttpURLConnection
import java.net.URI
@Controller
class MainController {
@GetMapping("/")
fun home(model: Model): String {
return "home.html"
}
@PostMapping("/webhook")
@ResponseBody
fun webhook(
@RequestParam("hook") hook_str: String,
@RequestBody body: String,
@RequestHeader("Content-Type") contentType: String,
model: Model
): String {
var hook = URI.create(hook_str).toURL();
System.out.println(hook);
for (h in State.arr) {
if (h.hook == hook) {
var newBody = h.template.replace("_DATA_", body);
var conn = hook.openConnection() as? HttpURLConnection;
if (conn === null) break;
conn.requestMethod = "POST";
conn.doOutput = true;
conn.setFixedLengthStreamingMode(newBody.length);
conn.setRequestProperty("Content-Type", contentType);
conn.connect()
conn.outputStream.use { os ->
os.write(newBody.toByteArray())
}
return h.response
}
}
return "{\"result\": \"fail\"}"
}
@PostMapping("/create", consumes = [MediaType.APPLICATION_JSON_VALUE])
@ResponseBody
fun create(@RequestBody body: StateType): String {
for(h in State.arr) {
if(body.hook == h.hook)
return "{\"result\": \"fail\"}"
}
State.arr.add(body)
return "{\"result\": \"ok\"}"
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package tf.irisc.chal.webwebhookhook
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class WebwebhookhookApplication
const val FLAG = "irisctf{test_flag}";
fun main(args: Array<String>) {
State.arr.add(StateType(
"http://example.com/admin",
"{\"data\": _DATA_, \"flag\": \"" + FLAG + "\"}",
"{\"response\": \"ok\"}"))
runApplication<WebwebhookhookApplication>(*args)
}
We can create a webhook, which will be stored in a global array. When we call the webhook, it iterates through all saved webhooks, comparing the URL.
The flag is stored for the example.com
domain, which we do not control.
The URLs are compared using the equality operator: if (h.hook == hook) {
. The operator, in Kotlin, is translated to the underlying Java function equals
.
It compares the ref (the hash, #
, part of the URL) and then the files.
It compares other stuff like protocol and path. The check we are interested in is the hostsEqual
function.
This is actually a DNS request for both hosts. If the DNS request is successful for both, then two hosts are considered equal if they both point to the same IP.
Going back to our challenge, after checking if (h.hook == hook) {
it makes a connection to the URL in the hook
variable, which is user-controlled, instead of using the safe h.hook
field.
The vulnerability here is that hook
can resolve to one IP in the if
condition, and to another IP when the connection is actually made. This is called DNS rebinding. You don’t need to buy a domain for this, there are free tools available online. I used this one: https://lock.cmpxchg8b.com/rebinder.html.
The only thing needed to do is create a script that continuously calls <challenge host>/webhook?hook=<rebinder address>
. In the favorable case, the rebinder will resolve to example.com
in the if
condition, and to our IP when the connection is being made. We can set up a web server on our IP that catches incoming POST data.