代碼安全掃描,PHP代碼安全雜談

 2023-10-15 阅读 20 评论 0

摘要:雖然PHP是世界上最好的語言,但是也有一些因為弱類型語言的安全性問題出現。WordPress歷史上就出現過由于PHP本身的缺陷而造成的一些安全性問題,如CVE-2014-0166?中的cookie偽造就是利用了PHP Hash比較的缺陷。 當然一般這種情況實戰中用到的不是很多,但是在CTF

不錯的縮略圖

雖然PHP是世界上最好的語言,但是也有一些因為弱類型語言的安全性問題出現。WordPress歷史上就出現過由于PHP本身的缺陷而造成的一些安全性問題,如CVE-2014-0166?中的cookie偽造就是利用了PHP Hash比較的缺陷。 當然一般這種情況實戰中用到的不是很多,但是在CTF競賽中卻是一個值得去考察的一個知識點,特此記錄總結之。

一、精度繞過缺陷

理論

在用PHP進行浮點數的運算中,經常會出現一些和預期結果不一樣的值,這是由于浮點數的精度有限。盡管取決于系統,PHP 通常使用?IEEE 754?雙精度格式,則由于取整而導致的最大相對誤差為?1.11e-16。非基本數學運算可能會給出更大誤差,并且要考慮到進行復合運算時的誤差傳遞。下面看一個有趣的例子:

代碼安全掃描,

以十進制能夠精確表示的有理數如?0.1?或?0.7,無論有多少尾數都不能被內部所使用的二進制精確表示,因此不能在不丟失一點點精度的情況下轉換為二進制的格式。這就會造成混亂的結果:例如,floor((0.1+0.7)*10)?通常會返回?7?而不是預期中的?8,因為該結果內部的表示其實是類似?7.9999999999999991118…

實踐

問鼎杯2017 老眼昏花網上很多write-up感覺就像是看著答案寫write-up,個人感覺真正的write-up中應該體現自己的思考在里面。

題目描述

php還有人用嗎?題目言簡意賅,讓我們把2017這個值傳遞給服務器。

考察點

  • PHP浮點精確度

write-up

what year is this?所以第一反應是直接給year參數賦值為2017:

?year=2017

然而結果如下:

有提示了,說明year這個參數是對的,但是2017中不可以出現7,這里如果不了解php精度的話,肯定是對2017進行各種編碼繞過,但是這里對編碼也進行過濾了:


所以最后一種可能就是利用PHP精度來繞過:

?year=2016.99999999999

php代碼如何運行,

二、類型轉換的缺陷

理論

PHP提供了is_numeric函數,用來變量判斷是否為數字。PHP弱類型語言的一個特性,當一個整形和一個其他類型行比較的時候,會先把其他類型intval數字化再比。

實踐

is_numeric()用于判斷是否是數字,通常配合數值判斷。

案例代碼

<?phperror_reporting(0);$flag = 'flag{1S_numer1c_Not_S4fe}';$id = $_GET['id']; is_numeric($id)?die("Sorry...."):NULL; if($id>665){ echo $flag; } ?> 

考察點

  • PHP類型轉換缺陷

write-up

分析下代碼:首先對GET方式提交的參數id的值進行檢驗。id通過is_numeric函數來判斷是否為數字,如果為數字的話,GG。如果不是數字的話,和665進行比較,id的值大于665的時候輸出flag
乍看上去又好像不可能這里,但是如果知道PHP弱類型語言的一個特性,當一個整形和一個其他類型行比較的時候,會先把其他類型intval數字化再比。這個特性的話就可以很好的繞過。

http://localhost/?id=666gg

PHP代碼發布、

三、松散比較符的缺陷

理論

php比較相等性的運算符有兩種,一種是嚴格比較,另一種是松散比較。

如果比較一個數字和字符串或者比較涉及到數字內容的字符串,則字符串會被轉換成數值并且比較按照數值來進行


嚴格比較符嚴格比較符,會先判斷兩種字符串的類型是否相等,再比較。

===   //全等
!==   //不全等

php繞過?

松散比較符松散比較符,會先將字符串類型轉換成相同,再比較。

==   //等于
!=   //不等


PHP 會根據變量的值,自動把變量轉換為正確的數據類型。這一點和C 和 C++ 以及 Java 之類的語言明顯不同。雖然這樣PHP方便了程序員,但是隨之而來卻會帶來一些安全性的問題。

一個簡單的例子

<?php$a = null;$b = false;echo $a==$b; echo "<br>"; $c = ""; $d = 0; echo $c==$d ?> 

由于php對變量自動轉換的特性,這里面的

$a==$b 與 $c==$d 均為真

php滲透?所以頁面輸出的結果為:

一個深入的例子

下面結合PHP 相等性比較缺陷再解釋下會好懂一點:

var_dump(0=="gg");  //true
var_dump(0==="gg"); //false var_dump(1=="gg"); //false 

0gg進行松散性質的不嚴格比較,會將gg轉換為數值,強制轉換,由于gg是字符串,轉化的結果是0,所以 輸出?true

代碼?0gg進行嚴格 性質的嚴格比較,這里的gg是字符串類型,和int類型的0不相等,所以輸出?false

0gg進行松散性質的不嚴格比較,會將gg轉換為數值,強制轉換,由于gg是字符串,轉化的結果是0,不等于1,所以輸出?false

var_dump(1=="1gg"); //true 
var_dump(1=="gg1"); //false 

11gg進行松散性質的不嚴格比較,這里1gg被強制轉換為int類型的時候會從字符串的第一位開始做判斷進行轉換,這里的1gg第一位是1,所以這里1gg被轉換為1,所以輸出?true

1gg1進行嚴格 性質的嚴格比較,字符串gg1的第一位不是數字,所以它被強制轉換為0,所以輸出?false

var_dump("0e123" == "0e456");  //true
var_dump("0e123" == "0eabc"); //flase 

php漏洞是什么原因。這里比較特殊,字符串中出現了0e,PHP手冊介紹如下:

當一個字符串欸當作一個數值來取值,其結果和類型如下:如果該字符串沒有包含'.','e','E'并且其數值值在整形的范圍之內 
該字符串被當作int來取值,其他所有情況下都被作為float來取值, 該字符串的開始部分決定了它的值,如果該字符串以合法的數值開始,則使用該數值,否則其值為0。 

0e1230e456相互不嚴格性質比較的時候,會將0e這類字符串識為科學技術法的數字,0的無論多少次方都是零,所以相等,輸出?true

0e1230eabc相互進行不嚴格性質比較的時候,本應該將0e這類字符串識為科學技術法的數字,但是這里的0e后面跟著的是abc,數學中科學計數的指數不可以包含字母。所以這里字符串中雖然是0e開頭,但是后面的abc卻不符合科學技法的規范,所以輸出是?false

實踐

md5繞過(Hash比較缺陷)南京郵電大學網絡攻防訓練平臺中一道比較經典的md5 collision題,關于這道題目的WriteUp網上很多,但是真正深入分析的少之又少~~

題目描述

md5 collision源碼

<?php$md51 = md5('QNKCDZO');$a = @$_GET['a'];$md52 = @md5($a);if(isset($a)){ if ($a != 'QNKCDZO' && $md51 == $md52) { echo "nctf{*****************}"; } else { echo "false!!!"; } } else{ echo "please input a"; } ?> 

考察點

  • 簡單的PHP代碼審計
  • PHP弱類型的Hash比較缺陷

write-up

從源碼中可以得輸入一個a的參數的變量,a首先不等于QNKCDZO并且a得md5值必須等于QNKCDZO加密后的md5值。 乍一看好像不可能存在這樣的值,但是這里QNKCDZO加密后的md5值為0e830400451993494058024219903391?這里是0e開頭的,在進行等于比較的時候,PHP把它當作科學計數法,0的無論多少次方都是零。 所以這里利用上面的弱類型的比較的缺陷來進行解題:?a=s155964671a

字符串加密后md5為0exxxx的字符串(x必須是10進制數字)列表

字符串md5
QNKCDZO0e830400451993494058024219903391
2406107080e462097431906509019562988736854
aabg7XSs0e087386482136013740957780965295
aabC9RqS0e041022518165728065344349536299
s878926199a0e545993274517709034328855841020

四、sha1() md5()加密函數漏洞缺陷

理論

md5()sha1()對一個數組進行加密將返回 NULL

實踐

Boston Key Party CTF 2015: Prudential

題目描述

I dont think sha1 isbroken.Prove me wrong.

題目給了一個登陸框:

考察點

  • sha1()函數漏洞缺陷

write-up

源代碼給出如下:

<html>
<head><title>level1</title> <link rel='stylesheet' href='style.css' type='text/css'> </head> <body> <?php require 'flag.php'; if (isset($_GET['name']) and isset($_GET['password'])) { if ($_GET['name'] == $_GET['password']) print 'Your password can not be your name.'; else if (sha1($_GET['name']) === sha1($_GET['password'])) die('Flag: '.$flag); else print '<p class="alert">Invalid password.</p>'; } ?> <section class="login"> <div class="title"> <a href="./index.txt">Level 1</a> </div> <form method="get"> <input type="text" required name="name" placeholder="Name"/><br/> <input type="text" required name="password" placeholder="Password" /><br/> <input type="submit"/> </form> </section> </body> </html> 

分析一下核心登錄代碼如下:

if ($_GET['name'] == $_GET['password'])print 'Your password can not be your name.'; else if (sha1($_GET['name']) === sha1($_GET['password'])) die('Flag: '.$flag); 

GET類型提交了兩個字段namepassword,獲得flag要求的條件是:

  • name != password
  • sha1(name) == sha1(password)

這個乍看起來這是不可能的,但是這里利用sha1()函數在處理數組的時候由于無法處理將返回NULL可以繞過if語句的驗證,if條件成立將獲得flag。 構造語句如下:

?name[]=a&password[]=b

這里符合了2個拿到flag的條件:

  • a不等于b
  • name和password由于是數組,經過sha1()函數嫁給后都返回NULL

拿到flag:?I_think_that_I_just_broke_sha1

拓展總結

經過驗證,不僅sha1()函數無法處理數組,這里md5()函數也有同樣的問題,在處理數組的時候,都將返回NULL
測試代碼如下:

<?php
error_reporting(0);
$flag = 'flag{I_think_that_I_just_broke_md5}';
if (isset($_GET['username']) and isset($_GET['password'])) { if ($_GET['username'] == $_GET['password']) print 'Your password can not be your username.'; else if (md5($_GET['username']) === sha1($_GET['password'])) die($flag); else print 'Invalid password'; } ?> 

這里面的核心代碼如下:

if ($_GET['username'] == $_GET['password'])
并且得滿足:
if (md5($_GET['username']) === sha1($_GET['password'])) 

同樣利用md5()函數無法處理數組的這個漏洞,構造get請求拿到flag:

?username[]=a&password[]=b

五、字符串處理函數漏洞缺陷

理論

  • strcmp()函數:比較兩個字符串(區分大小寫).

用法如下:

int strcmp ( string $str1 , string $str2 ) 

具體的用法解釋如下:

參數 `str1`第一個字符串。
參數 `str2`第二個字符串。
如果 `str1` 小于 `str2` 返回 `< 0`; 如果 `str1` 大于 `str2` 返回 `> 0`; 如果兩者相等,返回 0。 

這個函數接受到了不符合的類型,例如數組類型,函數將發生錯誤。但是在5.3之前的php中,顯示了報錯的警告信息后,將return 0?!!!! 也就是雖然報了錯,但卻判定其相等了。

  • ereg()函數:字符串正則匹配。
  • strpos()函數:查找字符串在另一字符串中第一次出現的位置,對大小寫敏感。

這2個函數都是用來處理字符串的,但是在傳入數組參數后都將返回NULL

實踐

Boston Key Party CTF 2015: Northeastern Univ

題目描述

Of course, a timing attack might be the answer, but Im quite sure that you can do better than that. 題目給了一個登陸框:

考察點

  • 字符串處理函數漏洞缺陷

write-up

給出源代碼如下:

<html>
<head><title>level3</title> <link rel='stylesheet' href='style.css' type='text/css'> </head> <body> <?php require 'flag.php'; if (isset($_GET['password'])) { if (strcmp($_GET['password'], $flag) == 0) die('Flag: '.$flag); else print '<p class="alert">Invalid password.</p>'; } ?> <section class="login"> <div class="title"> <a href="./index.txt">Level 3</a> </div> <form method="get"> <input type="text" required name="password" placeholder="Password" /><br/> <input type="submit"/> </form> </section> </body> </html> 

分析一下核心登錄代碼如下:

if (strcmp($_GET['password'], $flag) == 0)

這里使用了==松散比較了$flag和通過GET方式提交的password的值,如果想等的話,拿到flag。這里用的是==松散性質的比較,再利用字符串處理數組時將會報錯,在5.3之前的php中,顯示了報錯的警告信息后,將return 0。所有這里將password參數指定為數組,利用函數漏洞拿到flag:

拓展總結

除了strcmp()函數外,ereg()strpos()函數在處理數組的時候也會異常,返回NULL。測試代碼如下:

<?phperror_reporting(0);$flag = 'flag{P@ssw0rd_1s_n0t_s4fe_By_d0uble_Equ4ls}';if (isset ($_GET['password'])) { if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE) echo 'You password must be alphanumeric'; else if (strpos ($_GET['password'], '--') !== FALSE) die($flag); else echo 'Invalid password'; } ?> 

將參數password賦值一個數組傳遞進去:

http://localhost/?password[]=gg

ereg()函數是處理字符串的,傳入數組后返回NULLNULL和?FALSE,是不恒等(===)的,滿足第一個if條件;而strpos()函數也是處理字符串的,傳入數組后返回NULL,NULL!==FALSE,滿足條件,拿到flag:

六、parse_str函數變量覆蓋缺陷

理論

parse_str函數的作用就是解析字符串并注冊成變量,在注冊變量之前不會驗證當前變量是否存在,所以直接覆蓋掉已有變量。

void parse_str ( string $str [, array &$arr ] ) 

str 輸入的字符串。
arr 如果設置了第二個變量 arr,變量將會以數組元素的形式存入到這個數組,作為替代。

實踐

測試代碼:

<?php
error_reporting(0);
$flag = 'flag{V4ri4ble_M4y_Be_C0verEd}';
if (empty($_GET['b'])) { show_source(__FILE__); die(); }else{ $a = "www.sqlsec.com"; $b = $_GET['b']; @parse_str($b); if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) { echo $flag; }else{ exit('your answer is wrong~'); } } ?> 

考察點

  • parse_str變量覆蓋缺陷

write-up

找到核心代碼:

@parse_str($b);
這里使用了parse_str函數來傳遞b的變量值
if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) 這里用到的是文章上面的知識點md5()函數缺陷 

因為這里用到了parse_str函數來傳遞b,if的語句的條件是拿$a[0]來比較的,有因為這里的變量a的值已經三是固定的了:

$a = "www.sqlsec.com";

這里其實是我博客的地址~~ 不過不重要。 整體代碼乍看起來又不可能,但是利用變量覆蓋函數的缺陷這里可以對a的變量進行重新賦值,后面的的if語句再利用本文前面提到的md5()比較缺陷進行繞過:

http://localhost/?b=a[0]=240610708

參考文獻

  • PHP 比較運算符
  • PHP Float 浮點型
  • PHP 類型比較表
  • PHP 弱類型總結
  • PHP Hash比較存在缺陷,影響大量Web網站登錄認證、忘記密碼等關鍵業務
  • PHP代碼審計片段講解(入門代碼審計、CTF必備)
  • 淺談PHP弱類型安全
  • NJCTF2017 線上賽 web 題解
  • CTF之PHP黑魔法總結
  • Some features of PHP in CTF
  • PHP浮點數運算精度的問題
  • php strcmp()漏洞
  • 危險的is_numeric——PHPYun 2015-06-26 二次注入漏洞分析
  • 【代碼審計】變量覆蓋漏洞詳解

*本文作者:國光,轉載請注明FreeBuf.COM

轉載于:https://www.cnblogs.com/wwlww/p/8413515.html

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/5/138815.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息