parent
80dfee2a3f
commit
24a2ca806e
2 changed files with 315 additions and 0 deletions
@ -0,0 +1,315 @@ |
|||||||
|
+++ |
||||||
|
author = "Maik de Kruif" |
||||||
|
title = "Challenge 24 - AdventOfCTF" |
||||||
|
date = 2021-09-22T12:12:12+01:00 |
||||||
|
description = "A writeup for challenge 24 of AdventOfCTF." |
||||||
|
cover = "img/adventofctf/b915cb528c4b3d6fc4644f73ba8b829d.png" |
||||||
|
tags = [ |
||||||
|
"AdventOfCTF", |
||||||
|
"challenge", |
||||||
|
"ctf", |
||||||
|
"hacking", |
||||||
|
"writeup", |
||||||
|
"web", |
||||||
|
"python", |
||||||
|
"serialization", |
||||||
|
"blockchain", |
||||||
|
] |
||||||
|
categories = [ |
||||||
|
"ctf", |
||||||
|
"writeups", |
||||||
|
"hacking", |
||||||
|
] |
||||||
|
+++ |
||||||
|
|
||||||
|
- Points: 2400 |
||||||
|
|
||||||
|
## Description |
||||||
|
|
||||||
|
The final battle! The elves want revenge for their lost game! They have enhanced the tic-tac-toe game with blockchain technology. Cyber Security on the Blockchain will revolutionize everything, but most importantly ensure they will win this time. No cheating Santa! |
||||||
|
|
||||||
|
Visit <https://24.adventofctf.com> to start the challenge. |
||||||
|
|
||||||
|
## Recon |
||||||
|
|
||||||
|
When opening the page, we're greeted with what looks like the same screen as we had on [challenge 20]({{< ref "challenge_20.md" >}}). The only noticeable difference is the addition of `blockchain? True` in the footer. |
||||||
|
|
||||||
|
If we take a look at the source, we also find a comment with the following python code: |
||||||
|
|
||||||
|
```py |
||||||
|
# <!-- Development notes: Do not let santa see! |
||||||
|
|
||||||
|
def hash_string(string): |
||||||
|
return hashlib.md5(string.encode('utf-8')).hexdigest() |
||||||
|
|
||||||
|
def hash_row(row): |
||||||
|
conv = lambda i : i or ' ' |
||||||
|
res = [conv(i) for i in row] |
||||||
|
return hash_string(' '.join(res)) |
||||||
|
|
||||||
|
def hash_board(board): |
||||||
|
acc = "" |
||||||
|
for row in board: |
||||||
|
acc += hash_row(row) |
||||||
|
return acc |
||||||
|
|
||||||
|
def verify_chain(game): |
||||||
|
board=game["board"] |
||||||
|
chain = game["chain"] |
||||||
|
|
||||||
|
if len(chain) > 0: |
||||||
|
if board != chain[-1]["board"]: |
||||||
|
return False |
||||||
|
|
||||||
|
for i in range(len(chain)): |
||||||
|
block=chain[i] |
||||||
|
h = hash_board(block["board"]) |
||||||
|
h = hash_string(h + block["prev"]) |
||||||
|
if h != block["hash"]: |
||||||
|
return False |
||||||
|
return True |
||||||
|
|
||||||
|
# --> |
||||||
|
``` |
||||||
|
|
||||||
|
Just like in challenge 20, we alse find a game cookie: |
||||||
|
|
||||||
|
```text |
||||||
|
game=gAN9cQAoWAUAAABib2FyZHEBXXECKF1xAyhYAQAAAE9xBGgETmVdcQUoaARYAQAAAFhxBmgGZV1xByhoBGgGaAZlZVgEAAAAdHVybnEIaAZYCAAAAGZpbmlzaGVkcQmJWAYAAAB3aW5uZXJxClgAAAAAcQtYBAAAAHNhbmVxDIhYCgAAAGJsb2NrY2hhaW5xDYhYBQAAAGNoYWlucQ5dcQ8ofXEQKGgBXXERKF1xEihOTk5lXXETKE5OTmVdcRQoTk5oBmVlWAQAAABwcmV2cRVYIAAAAGNlZjIxNWM1YmU4Y2Y2M2ZjZjNkNDNlY2YyNTEwYjMzcRZYBAAAAGhhc2hxF1ggAAAAZTdkYzhlMWY3YTY3ODhiYzBjYjY4NDE1MzhiMjE2ZThxGHV9cRkoaAFdcRooXXEbKGgETk5lXXEcKE5OTmVdcR0oTk5oBmVlaBVoGGgXWCAAAABmYzkzMjM2YjVlZWE1ZjFkNTVlMmI1YjMwOGQ2NzM5MHEedX1xHyhoAV1xIChdcSEoaAROTmVdcSIoTmgGTmVdcSMoTk5oBmVlaBVoHmgXWCAAAABhOGRjMGQzZGEyOTBkMWU4OTRlYWFmZmNiOTgzOThjOXEkdX1xJShoAV1xJihdcScoaARoBE5lXXEoKE5oBk5lXXEpKE5OaAZlZWgVaCRoF1ggAAAAZTc0ZjViMjJmNTIxM2JhNGMyNDQ5NzU5Y2U5MWMyYWFxKnV9cSsoaAFdcSwoXXEtKGgEaAROZV1xLihOaAZoBmVdcS8oTk5oBmVlaBVoKmgXWCAAAABlZjI1NTE0ZGZmYmY4MjQ3Y2ZmNjA2M2JlOTBmMmQ1NHEwdX1xMShoAV1xMihdcTMoaARoBE5lXXE0KGgEaAZoBmVdcTUoTk5oBmVlaBVoMGgXWCAAAABlM2E0YzAzN2JkZjE1NGIzNDRlZDliZDE2NDNhNjI5ZHE2dX1xNyhoAV1xOChdcTkoaARoBE5lXXE6KGgEaAZoBmVdcTsoTmgGaAZlZWgVaDZoF1ggAAAAYzI0MGZhMTYxNzM3Yzk2N2VjZTVmZDk0NjcyYWIwZjhxPHV9cT0oaAFdcT4oXXE/KGgEaAROZV1xQChoBGgGaAZlXXFBKGgEaAZoBmVlaBVoPGgXWCAAAAAyNGJhNzE1ZGMwNTY4M2NlNDViOWUxOTFlZDE4OGI5Y3FCdWV1Lg== |
||||||
|
``` |
||||||
|
|
||||||
|
## Finding the vulnerability |
||||||
|
|
||||||
|
Using the same method as in challenge 20, we can take a look at the board: |
||||||
|
|
||||||
|
```py |
||||||
|
In [1]: import pickle |
||||||
|
|
||||||
|
In [2]: import base64 |
||||||
|
|
||||||
|
In [3]: pickle.loads(base64.b64decode("gAN9cQAoWAUAAABib2FyZHEBXXECKF1xAyhYAQAAAE9xBGgETmVdcQUoaARYAQAAAFhxBmgGZV1xByhoBGgGaAZlZVgEAAAAdHVybnEIaAZYCAAAAGZpbmlzaGVkcQmJWAYAAAB3aW5uZXJxClgAAAAAcQtYBAAAAHNhbmVxDIhYCgAAAGJsb2NrY2hhaW5xDYhYBQAAAGNoYWlucQ5dcQ8ofXEQKGgBXXERKF1xEihOTk5lXXETKE5OTmVdcRQoTk5oBmVlWAQAAABwcmV2cRVYIAAAAGNlZjIxNWM1YmU4Y2Y2M2ZjZjNkNDNlY2YyNTEwYjMzcRZYBAAAAGhhc2hxF1ggAAAAZTdkYzhlMWY3YTY3ODhiYzBjYjY4NDE1MzhiMjE2ZThxGHV9cRkoaAFdcRooXXEbKGgETk5lXXEcKE5OTmVdcR0oTk5oBmVlaBVoGGgXWCAAAABmYzkzMjM2YjVlZWE1ZjFkNTVlMmI1YjMwOGQ2NzM5MHEedX1xHyhoAV1xIChdcSEoaAROTmVdcSIoTmgGTmVdcSMoTk5oBmVlaBVoHmgXWCAAAABhOGRjMGQzZGEyOTBkMWU4OTRlYWFmZmNiOTgzOThjOXEkdX1xJShoAV1xJihdcScoaARoBE5lXXEoKE5oBk5lXXEpKE5OaAZlZWgVaCRoF1ggAAAAZTc0ZjViMjJmNTIxM2JhNGMyNDQ5NzU5Y2U5MWMyYWFxKnV9cSsoaAFdcSwoXXEtKGgEaAROZV1xLihOaAZoBmVdcS8oTk5oBmVlaBVoKmgXWCAAAABlZjI1NTE0ZGZmYmY4MjQ3Y2ZmNjA2M2JlOTBmMmQ1NHEwdX1xMShoAV1xMihdcTMoaARoBE5lXXE0KGgEaAZoBmVdcTUoTk5oBmVlaBVoMGgXWCAAAABlM2E0YzAzN2JkZjE1NGIzNDRlZDliZDE2NDNhNjI5ZHE2dX1xNyhoAV1xOChdcTkoaARoBE5lXXE6KGgEaAZoBmVdcTsoTmgGaAZlZWgVaDZoF1ggAAAAYzI0MGZhMTYxNzM3Yzk2N2VjZTVmZDk0NjcyYWIwZjhxPHV9cT0oaAFdcT4oXXE/KGgEaAROZV1xQChoBGgGaAZlXXFBKGgEaAZoBmVlaBVoPGgXWCAAAAAyNGJhNzE1ZGMwNTY4M2NlNDViOWUxOTFlZDE4OGI5Y3FCdWV1Lg==")) |
||||||
|
|
||||||
|
``` |
||||||
|
|
||||||
|
This time our board is quite a bit larger: |
||||||
|
|
||||||
|
{{< code language="py" title="Board" >}} |
||||||
|
|
||||||
|
```py |
||||||
|
{ |
||||||
|
"blockchain": True, |
||||||
|
"board": [ |
||||||
|
["O", "O", None], |
||||||
|
["O", "X", "X"], |
||||||
|
[None, "X", "X"] |
||||||
|
], |
||||||
|
"chain": [ |
||||||
|
{ |
||||||
|
"board": [ |
||||||
|
[None, None, None], |
||||||
|
[None, None, None], |
||||||
|
[None, None, "X"] |
||||||
|
], |
||||||
|
"hash": "e7dc8e1f7a6788bc0cb6841538b216e8", |
||||||
|
"prev": "cef215c5be8cf63fcf3d43ecf2510b33" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"board": [ |
||||||
|
["O", None, None], |
||||||
|
[None, None, None], |
||||||
|
[None, None, "X"] |
||||||
|
], |
||||||
|
"hash": "fc93236b5eea5f1d55e2b5b308d67390", |
||||||
|
"prev": "e7dc8e1f7a6788bc0cb6841538b216e8" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"board": [ |
||||||
|
["O", None, None], |
||||||
|
[None, "X", None], |
||||||
|
[None, None, "X"] |
||||||
|
], |
||||||
|
"hash": "a8dc0d3da290d1e894eaaffcb98398c9", |
||||||
|
"prev": "fc93236b5eea5f1d55e2b5b308d67390" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"board": [ |
||||||
|
["O", "O", None], |
||||||
|
[None, "X", None], |
||||||
|
[None, None, "X"] |
||||||
|
], |
||||||
|
"hash": "e74f5b22f5213ba4c2449759ce91c2aa", |
||||||
|
"prev": "a8dc0d3da290d1e894eaaffcb98398c9" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"board": [ |
||||||
|
["O", "O", None], |
||||||
|
[None, "X", "X"], |
||||||
|
[None, None, "X"] |
||||||
|
], |
||||||
|
"hash": "ef25514dffbf8247cff6063be90f2d54", |
||||||
|
"prev": "e74f5b22f5213ba4c2449759ce91c2aa" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"board": [ |
||||||
|
["O", "O", None], |
||||||
|
["O", "X", "X"], |
||||||
|
[None, None, "X"] |
||||||
|
], |
||||||
|
"hash": "e3a4c037bdf154b344ed9bd1643a629d", |
||||||
|
"prev": "ef25514dffbf8247cff6063be90f2d54" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"board": [ |
||||||
|
["O", "O", None], |
||||||
|
["O", "X", "X"], |
||||||
|
[None, "X", "X"] |
||||||
|
], |
||||||
|
"hash": "c240fa161737c967ece5fd94672ab0f8", |
||||||
|
"prev": "e3a4c037bdf154b344ed9bd1643a629d" |
||||||
|
} |
||||||
|
], |
||||||
|
"finished": False, |
||||||
|
"sane": True, |
||||||
|
"turn": "O", |
||||||
|
"winner": "" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
{{< /code >}} |
||||||
|
|
||||||
|
We can see that a `chain` value has been added. From the title of this challenge, we can say that this is the blockchain that we likely have to bypass. |
||||||
|
|
||||||
|
## Exploit |
||||||
|
|
||||||
|
Cracking this shouldn't be too hard. Let's start by copying our script from challenge 20 and resetting the chain: |
||||||
|
|
||||||
|
```py |
||||||
|
import base64 |
||||||
|
import pickle |
||||||
|
|
||||||
|
board_b64 = "gAN9cQAoWAUAAABib2FyZHEBXXECKF1xAyhYAQAAAE9xBGgETmVdcQUoaARYAQAAAFhxBmgGZV1xByhOaAZoBmVlWAQAAAB0dXJucQhoBFgIAAAAZmluaXNoZWRxCYlYBgAAAHdpbm5lcnEKWAAAAABxC1gEAAAAc2FuZXEMiFgKAAAAYmxvY2tjaGFpbnENiFgFAAAAY2hhaW5xDl1xDyh9cRAoaAFdcREoXXESKE5OTmVdcRMoTk5OZV1xFChOTmgGZWVYBAAAAHByZXZxFVggAAAAY2VmMjE1YzViZThjZjYzZmNmM2Q0M2VjZjI1MTBiMzNxFlgEAAAAaGFzaHEXWCAAAABlN2RjOGUxZjdhNjc4OGJjMGNiNjg0MTUzOGIyMTZlOHEYdX1xGShoAV1xGihdcRsoaAROTmVdcRwoTk5OZV1xHShOTmgGZWVoFWgYaBdYIAAAAGZjOTMyMzZiNWVlYTVmMWQ1NWUyYjViMzA4ZDY3MzkwcR51fXEfKGgBXXEgKF1xIShoBE5OZV1xIihOaAZOZV1xIyhOTmgGZWVoFWgeaBdYIAAAAGE4ZGMwZDNkYTI5MGQxZTg5NGVhYWZmY2I5ODM5OGM5cSR1fXElKGgBXXEmKF1xJyhoBGgETmVdcSgoTmgGTmVdcSkoTk5oBmVlaBVoJGgXWCAAAABlNzRmNWIyMmY1MjEzYmE0YzI0NDk3NTljZTkxYzJhYXEqdX1xKyhoAV1xLChdcS0oaARoBE5lXXEuKE5oBmgGZV1xLyhOTmgGZWVoFWgqaBdYIAAAAGVmMjU1MTRkZmZiZjgyNDdjZmY2MDYzYmU5MGYyZDU0cTB1fXExKGgBXXEyKF1xMyhoBGgETmVdcTQoaARoBmgGZV1xNShOTmgGZWVoFWgwaBdYIAAAAGUzYTRjMDM3YmRmMTU0YjM0NGVkOWJkMTY0M2E2MjlkcTZ1fXE3KGgBXXE4KF1xOShoBGgETmVdcTooaARoBmgGZV1xOyhOaAZoBmVlaBVoNmgXWCAAAABjMjQwZmExNjE3MzdjOTY3ZWNlNWZkOTQ2NzJhYjBmOHE8dWV1Lg==" |
||||||
|
|
||||||
|
game = pickle.loads(base64.b64decode(board_b64)) |
||||||
|
|
||||||
|
game["board"] = [['X', 'X', 'X'], ['O', None, 'O'], [None, None, None]] |
||||||
|
game["chain"] = [] |
||||||
|
game["finished"] = True |
||||||
|
game["winner"] = 'X' |
||||||
|
game["turn"] = 'O' |
||||||
|
|
||||||
|
print(base64.b64encode(pickle.dumps(data))) |
||||||
|
``` |
||||||
|
|
||||||
|
Sadly, just submitting the result of this script doesn't work. We have to fill the chain. |
||||||
|
|
||||||
|
To find out how the chain works, let's take another look at the `verify_chain()` function: |
||||||
|
|
||||||
|
```py |
||||||
|
def verify_chain(game): |
||||||
|
board=game["board"] |
||||||
|
chain = game["chain"] |
||||||
|
|
||||||
|
if len(chain) > 0: |
||||||
|
if board != chain[-1]["board"]: |
||||||
|
return False |
||||||
|
|
||||||
|
for i in range(len(chain)): |
||||||
|
block=chain[i] |
||||||
|
h = hash_board(block["board"]) |
||||||
|
h = hash_string(h + block["prev"]) |
||||||
|
if h != block["hash"]: |
||||||
|
return False |
||||||
|
return True |
||||||
|
``` |
||||||
|
|
||||||
|
We can see that, to verify the game, the function loops through all the blocks in the chain. It then calculates a hash based on the board, and the previous hash and compares that to the actual hash in the block (the user provided one). |
||||||
|
|
||||||
|
To crack this, we can simply reverse this algorithm. To do this, I grabbed the script from the HTML source, and added a crack method like so: |
||||||
|
|
||||||
|
{{< code language="py" title="solve.py" >}} |
||||||
|
|
||||||
|
```python |
||||||
|
import hashlib |
||||||
|
import pickle |
||||||
|
import base64 |
||||||
|
|
||||||
|
|
||||||
|
def load_base64(base64_string): |
||||||
|
return pickle.loads(base64.b64decode(base64_string)) |
||||||
|
|
||||||
|
|
||||||
|
def export_base64(game): |
||||||
|
return base64.b64encode(pickle.dumps(game)).decode() |
||||||
|
|
||||||
|
|
||||||
|
def hash_string(string): |
||||||
|
return hashlib.md5(string.encode('utf-8')).hexdigest() |
||||||
|
|
||||||
|
|
||||||
|
def hash_row(row): |
||||||
|
def conv(i): return i or ' ' |
||||||
|
res = [conv(i) for i in row] |
||||||
|
return hash_string(' '.join(res)) |
||||||
|
|
||||||
|
|
||||||
|
def hash_board(board): |
||||||
|
acc = "" |
||||||
|
for row in board: |
||||||
|
acc += hash_row(row) |
||||||
|
return acc |
||||||
|
|
||||||
|
|
||||||
|
def verify_chain(game): |
||||||
|
board = game["board"] |
||||||
|
chain = game["chain"] |
||||||
|
|
||||||
|
if len(chain) > 0: |
||||||
|
if board != chain[-1]["board"]: |
||||||
|
return False |
||||||
|
|
||||||
|
for block in chain: |
||||||
|
h = hash_board(block["board"]) |
||||||
|
h = hash_string(h + block["prev"]) |
||||||
|
if h != block["hash"]: |
||||||
|
return False |
||||||
|
return True |
||||||
|
|
||||||
|
|
||||||
|
def crack(game): |
||||||
|
game["board"] = [['X', 'X', 'X'], ['O', None, 'O'], [None, None, None]] |
||||||
|
game["chain"] = [] |
||||||
|
game["finished"] = True |
||||||
|
game["winner"] = 'X' |
||||||
|
game["turn"] = 'O' |
||||||
|
|
||||||
|
board = game["board"] |
||||||
|
|
||||||
|
h = hash_board(board) |
||||||
|
h = hash_string(h) |
||||||
|
|
||||||
|
game["chain"].append({ |
||||||
|
"board": board, |
||||||
|
"hash": h, |
||||||
|
"prev": "" |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
game = load_base64("gAN9cQAoWAUAAABib2FyZHEBXXECKF1xAyhYAQAAAE9xBGgETmVdcQUoaARYAQAAAFhxBmgGZV1xByhOaAZoBmVlWAQAAAB0dXJucQhoBFgIAAAAZmluaXNoZWRxCYlYBgAAAHdpbm5lcnEKWAAAAABxC1gEAAAAc2FuZXEMiFgKAAAAYmxvY2tjaGFpbnENiFgFAAAAY2hhaW5xDl1xDyh9cRAoaAFdcREoXXESKE5OTmVdcRMoTk5OZV1xFChOTmgGZWVYBAAAAHByZXZxFVggAAAAY2VmMjE1YzViZThjZjYzZmNmM2Q0M2VjZjI1MTBiMzNxFlgEAAAAaGFzaHEXWCAAAABlN2RjOGUxZjdhNjc4OGJjMGNiNjg0MTUzOGIyMTZlOHEYdX1xGShoAV1xGihdcRsoaAROTmVdcRwoTk5OZV1xHShOTmgGZWVoFWgYaBdYIAAAAGZjOTMyMzZiNWVlYTVmMWQ1NWUyYjViMzA4ZDY3MzkwcR51fXEfKGgBXXEgKF1xIShoBE5OZV1xIihOaAZOZV1xIyhOTmgGZWVoFWgeaBdYIAAAAGE4ZGMwZDNkYTI5MGQxZTg5NGVhYWZmY2I5ODM5OGM5cSR1fXElKGgBXXEmKF1xJyhoBGgETmVdcSgoTmgGTmVdcSkoTk5oBmVlaBVoJGgXWCAAAABlNzRmNWIyMmY1MjEzYmE0YzI0NDk3NTljZTkxYzJhYXEqdX1xKyhoAV1xLChdcS0oaARoBE5lXXEuKE5oBmgGZV1xLyhOTmgGZWVoFWgqaBdYIAAAAGVmMjU1MTRkZmZiZjgyNDdjZmY2MDYzYmU5MGYyZDU0cTB1fXExKGgBXXEyKF1xMyhoBGgETmVdcTQoaARoBmgGZV1xNShOTmgGZWVoFWgwaBdYIAAAAGUzYTRjMDM3YmRmMTU0YjM0NGVkOWJkMTY0M2E2MjlkcTZ1fXE3KGgBXXE4KF1xOShoBGgETmVdcTooaARoBmgGZV1xOyhOaAZoBmVlaBVoNmgXWCAAAABjMjQwZmExNjE3MzdjOTY3ZWNlNWZkOTQ2NzJhYjBmOHE8dWV1Lg==") |
||||||
|
crack(game) |
||||||
|
print(export_base64(game)) |
||||||
|
``` |
||||||
|
|
||||||
|
{{< /code >}} |
||||||
|
|
||||||
|
After running this script, we get the following result: |
||||||
|
|
||||||
|
```text |
||||||
|
gASVsgAAAAAAAAB9lCiMBWJvYXJklF2UKF2UKIwBWJRoBGgEZV2UKIwBT5ROaAZlXZQoTk5OZWWMBHR1cm6UaAaMCGZpbmlzaGVklIiMBndpbm5lcpRoBIwEc2FuZZSIjApibG9ja2NoYWlulIiMBWNoYWlulF2UfZQojAVib2FyZJRoAowEaGFzaJSMIDZkZTM2NDM0OTAwZTI4YTdlYWYwNDhhYjBhY2JlNjA0lIwEcHJldpSMAJR1YXUu |
||||||
|
``` |
||||||
|
|
||||||
|
When we set this and refresh the page, we get the following message: `"Game goes to: X NOVI{blockchain_cyb3r_security} Thank you for playing Advent of CTF! I hope you have a great christmas!"`. |
||||||
|
|
||||||
|
## Solution |
||||||
|
|
||||||
|
We got the flag! It is `NOVI{blockchain_cyb3r_security}`. |
After Width: | Height: | Size: 546 KiB |
Loading…
Reference in new issue