变量覆盖
extract()
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。条件:若有EXTR_SKIP则不行。
<?php
$a = "Original";
$my_array = array("a" => "Cat","b" => "Dog", "c" => "Horse");
extract($my_array);
echo "\$a = $a; \$b = $b; \$c = $c";
?>
这里原来是
a
是
o
r
i
g
i
n
a
l
,
后
面
通
过
e
x
t
r
a
c
t
把
a是original,后面通过extract把
a是original,后面通过extract把a覆盖变成了Cat了,所以这里把原来的变量给覆盖了。
<?php
$flag='xxx';
extract($_GET);
if(isset($shiyan))
{
$content=trim(file_get_contents($flag));
if($shiyan==$content)
{
echo'ctf{xxx}';
}
else
{
echo'Oh.no';
}
}
parse_str()
解析字符串并注册成变量
$b=1;
Parse_str('b=2');
Print_r($b);
import_request_variables()
将 GET/POST/Cookie 变量导入到全局作用域中,全局变量注册。
在5.4之后被取消,只可在4-4.1.0和5-5.4.0可用。
import_request_variable("p", "post_");
import_request_variable("gp", "gp_");
import_request_variable("cg", "cg_");
$$变量覆盖
<?
$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变量。
echo $foobar;
if (ini_get("register_globals")) foreach($_REQUEST as $k=>$v) unset(${$k});
print $a;
print $_GET[b];
绕过过滤的空白字符
控制码
"\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。
intval整数溢出
php整数上限溢出绕过intval
intval 函数最大的值取决于操作系统。 32 位系统最大带符号的 integer 范围是 -2147483648 到 2147483647。举例,在这样的系统上, intval(‘1000000000000’) 会返回 2147483647。 64 位系统上,最大带符号的 integer 值是 9223372036854775807。
intval 四舍五入
<?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]);
$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"]))
多重加密
题目中有:
$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);
?>
截断
iconv 异常字符截断
$a='1'.char(130).'2';
echo iconv("UTF-8","gbk",$a);
echo iconv('GB2312', 'UTF-8', $str);
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>';
}
}
move_uploaded_file 用\0截断
5.4.x<= 5.4.39, 5.5.x<= 5.5.23, 5.6.x <= 5.6.7 原来在高版本(受影响版本中),PHP把长度比较的安全检查逻辑给去掉了,导致了漏洞的发生 cve: https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-2348
move_uploaded_file(KaTeX parse error: Undefined control sequence: \x at position 39: …,"/tmp/test.php\?x?00.jpg") 上传抓包修改…_FILES[‘xx’][‘name’]存储的字符串是a.php,不会包含\0截断之后的字符,因此并不影响代码的验证逻辑。 但是如果通过$_REQUEST方式获取的,则可能出现扩展名期望值不一致的情况,造成“任意文件上传”。
inclue用?截断
<?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版本上测试通过。
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
弱类型比较
以下等式会成立
'' == 0 == false
'123' == 123
'abc' == 0
'123a' == 123
'0x01' == 1
'0e123456789' == '0e987654321'
[false] == [0] == [NULL] == ['']
NULL == false == 0
true == 1
==、>、<的弱类型比较
这里用到了PHP弱类型的一个特性,当一个整形和一个其他类型行比较的时候,会先把其他类型转换成整型再比。
<?php
is_numeric(@$a["a1"])?die("nope"):NULL;
if(@$a["a1"]){
var_dump($a);
($a["a1"]>1336)?$v1=1:NULL;
}
var_dump($v1);
switch 弱类型
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')
md5('QNKCDZO')
0e 纯数字这种格式的字符串在判断相等的时候会被认为是科学计数法的数字,先做字符串到数字的转换。
md5('240610708')==md5('QNKCDZO');
md5('240610708')===md5('QNKCDZO');
这样的对应数值还有:
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”的字符串
define('FLAG', 'pwnhub{THIS_IS_FLAG}');
if ($_GET['s1'] != $_GET['s2']
&& md5($_GET['s1']) == md5($_GET['s2'])) {
echo "success, flag:" . FLAG;
}
$name = addslashes($_POST['name']);
$r = $db->get_row("SELECT `pass` FROM `user` WHERE `name`='{$name}'");
if ($r['pass'] === md5($_POST['pass'])) {
echo "success";
}
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,则相等了。
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
]
,
c[1],
c[1],d)
strcmp漏洞3: 判断使用的是 ==
而判断使用的是==,当NULL==0是 bool(true)
in_array,array_search 弱类型比较
松散比较下,任何string都等于true:
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。可通过传入数组,返回错误
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
<?php
$flag = "flag";
if (isset ($_GET['nctf'])) {
if (@ereg ("^[1-9]+$", $_GET['nctf']) === FALSE)
echo '必须输入数字才行';
else if (strpos ($_GET['nctf'], '#biubiubiu') !== FALSE)
die('Flag: '.$flag);
else
echo '骚年,继续努力吧啊~';
}
十六进制与十进制比较
== 两边的十六进制与十进制比较,是可以相等的。
?password=0xdeadc0de
<?php
error_reporting(0);
function noother_says_correct($temp)
{
$flag = 'flag{test}';
$one = ord('1');
$nine = ord('9');
$number = '3735929054';
for ($i = 0; $i < strlen($number); $i++)
{
$digit = ord($temp{$i});
if ( ($digit >= $one) && ($digit <= $nine) )
{
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值为真,也就绕过了检测。
$sql = "SELECT * FROM admin WHERE username = admin pass = '".md5($password,true)."'";
switch没有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;
}
}
反序列化和文件包含太多了,不细说了
str_replace路径穿越
原理 str_replace的过滤方式为其search参数数组从左到右一个一个过滤。
$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;
short_open_tag=on 短标签
原理: 当 php.ini 的short_open_tag=on时,PHP支持短标签,默认情况下为off; 格式为: –> <?xxx;
Go0s@ubuntu:~$ cat test.php
<?="helloworld";
Go0s@ubuntu:~$ curl 127.0.0.1/test.php
helloworld
file_put_contents第二个参数传入数组
file_put_contents(file,data,mode,context) file 必需。规定要写入数据的文件。如果文件不存在,则创建一个新文件。 data 可选。规定要写入文件的数据。可以是字符串、数组或数据流。如果是数组的话,将被连接成字符串再进行写入。
<?php
$a = $_GET['data'];
$file = $_GET['filename'];
$current = file_get_contents($file);
file_put_contents($file, $a);
单引号和双引号
原理:单引号或双引号都可以用来定义字符串。但只有双引号会调用解析器。
$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';
$abc='I love u';
echo $abc
echo '$abc'
echo "$abc"
$a="${@phpinfo()}";
<?php $a="${@phpinfo()}";?>
命令执行函数
system() exec() passthru() pcntl_exec() shell_exec() echo `whoami`; //反引号调用shell_exec()函数 popen()和proc_open() //不会返回结果 array_map(
a
r
r
,
arr,
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 指定可执行文件的路径
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模式执行命令
preg_replace("/\[(.*)]/e",'\\1',$_GET['str']);
动态函数执行
call_user_func
call_user_func_array
?a=assert
call_user_func($_GET['a'],$b);
eval()和assert()代码执行
当assert()的参数为字符串时 可执行PHP代码。 区别:assert可以不加;,eval不可以不加;。
|