免责声明:本文章仅用于交流学习,因文章内容而产生的任何违法&未授权行为,与文章作者无关!!!
附:完整笔记目录~
ps:本人小白,笔记均在个人理解基础上整理,若有错误欢迎指正!
引子:本章主要介绍一些由PHP自身语言特性可能产生的脆弱性,该内容往往被应用于PHP CTF入门题中,但在PHP Web开发时也可能被使用。
==
== 是php中的比较运算符,用于判断 == 左右两边的值是否相等。若两边的值类型不同,则会优先将两边的值类型转换为同一类型后再进行比较,也称这种比较方式为松散比较。
这其中使用最多也是最容易产生误解的就是数字、数字字符串、字符串之间的比较。这里给出官方规则:
If both operands are numeric strings, or one operand is a number and the other one is a numeric string, then the comparison is done numerically. These rules also apply to the switch statement.
简单来说,就是当数字与数字字符串,或者数字字符串与数字字符串之间进行比较时,都会被转换为数字后再比较,这些规则同样适用于switch。有没有感觉官方给出的内容有些含糊呢?这里举几个例子:
// 在松散比较中,以下格式的字符串会被转换为什么数字呢?如:
"sjjjer"
"1999sj"
"1999e9r"
"1999.9er"
我们分别来测试一下:
$data1 = "sjjjer";
$data2 = 0;
echo $data1 == $data2 ? "true" : "false";
// true --> php版本为7.3
// false --> php版本为8.2
// 从官方给出的解释来看,在php8版本前字符串默认被转换为数字0参与比较,8版本后被修复
$data1 = "1999sj19";
$data2 = 1999;
echo $data1 == $data2 ? "true" : "false";
// true
// 可以看到,当字符串中同时出现字母与数字时,且数字在前,
// 则在比较时会默认将字符串转换为出现第一个字母前的数字
$data1 = "1999e9r";
$data2 = 1999;
echo $data1 == $data2 ? "true" : "false";
// false
// 为什么此时松散比较结果为false呢?
// 是由于1999e9在转换为数字参与比较时,其实际值为1999 * 10^9,e是科学计数法的标识。
// So 我们可以修改1999的值为1999000000000再尝试比较
$data3 = 1999000000000;
echo $data3 == $data1 ? "true" : "false";
// true
$data1 = "1999.9er";
$data2 = 1999;
echo $data1 == $data2 ? "true" : "false";
// false
// 由于在比较时会将字符串转换为出现第一个字母前的数字
// 也就是 "1999.9er" --> 1999.9 (浮点数)
// 当我们修改1999为1999.9时,再参与比较
$data3 = 1999.9;
echo $data1 == $data3 ? "true" : "false";
// true
switch()
上述所提松散比较时的类型转换规则同样适用于switch分支结构,也就说明php中switch结构在进行数据比较时也采用松散比较,这里来测试一下。
demo:
echo "请输入数据:";
$data1 = fgets(STDIN);
// 由于fgets()从键盘获取到的数据默认携带 \n,
// 因此需使用trim()去除
$data1 = trim($data1);
switch ($data1) {
case 0:
echo 0;
break;
case 19:
echo 19;
break;
case "sj":
echo "sj";
break;
default:
echo "数据未成功匹配!";
}
// fgets()为获取用户所输入数据,默认返回值类型为字符串,
// 也就是说data1的数据类型为字符串,
// next 进入switch分支结构,
// 该分支结构实质为字符串与数字、字符串与字符串的松散比较
/*请输入数据:sj --> php版本7.3
0
请输入数据:19er
19
请输入数据:sj --> php版本8.2
sj*/
md5()
已知md5加密后会生成固定32位字符串。而在php的身份验证中,常常会存在这样的逻辑:获取用户输入密码并将其转换为md5值,随后再与数据库所存储用户密码比较(数据库中用户密码往往加密存储),若相等则成功验证身份。
若开发者所使用比较运算符为 == ,也就是本文所提松散比较,则可能会出现以下情况:
我们来验证一下:
echo "请输入您的密码:";
$input = trim(fgets(STDIN));
$password = md5("000dSb96");
if (md5($input) == $password) {
echo "恭喜您!密码输入正确";
} else {
echo "Sorry,您的密码有误";
}
// 请输入您的密码:001st0X5
// 恭喜您!密码输入正确
若想获取更多满足要求的字符串,可以写个脚本慢慢跑,这里给出我自己的脚本:
import hashlib
import itertools
import string
# 定义函数,生成所有可能的8-16位字符串
def generate_strings():
# 所有可用的字符:数字和字母
chars = string.digits + string.ascii_lowercase + string.ascii_uppercase
# 生成长度从8到16的字符串
for length in range(8, 17):
# 生成当前长度的所有可能字符串
for s in itertools.product(chars, repeat=length):
yield ''.join(s)
# 定义函数,检查MD5值是否以0e开头且后续为数字
def check_md5(md5_hash):
# 判断MD5值是否以"0e"开头,且后续部分全为数字
return md5_hash.startswith("0e") and md5_hash[2:].isdigit()
# 主程序
def main():
# 打开result.txt文件以写入结果
with open("result.txt", "w") as result_file:
# 符合条件的字符串数量
found_count = 0
# 遍历生成的所有字符串
for s in generate_strings():
# 对字符串进行MD5加密
md5_hash = hashlib.md5(s.encode('utf-8')).hexdigest()
# 检查MD5是否满足条件
if check_md5(md5_hash):
# 打印符合条件的字符串和MD5值
print(f"{s} {md5_hash}")
# 写入到文件中
result_file.write(f"{s} {md5_hash}\n")
found_count += 1
# 如果已经找到了10个符合条件的字符串,停止执行
if found_count >= 10:
break
if __name__ == "__main__":
main()
10分钟就跑出来了五条,看来还有很大的优化空间。。。
# 结果展示
000dSb96 0e656002536496242128899000718690
001st0X5 0e632311920444249596217934156166
001uTksg 0e012344733002178183175029976663
001yLWsp 0e561475551818519587284167859133
001CKN1u 0e364973363767512736620399322212
0029F2eL 0e819654840882947331243228254063
002zDdF8 0e693440086514416381258215413583
0031LEiY 0e050990879989909849271876008654
003kjZ6a 0e813458376037199230741560480845
003mHOrg 0e186417107900720544959665557875
要是懒得用脚本跑,也可以参考:https://blog.csdn.net/baidu_41871794/article/details/83750615
,这篇博文作者也给了一些例子。
strcmp()
php中的字符串比较函数,strcmp($str1, $str2)会逐个字符比较字符串str1,str2的ASCII值,若str1 < str2 则返回值<0,若str1 > str2 则返回值>0,若 str1 = str2 返回值为0。
而在php 8版本之前,若所比较值类型为非字符串时,此时strcmp()函数报错且return 0,也就是说虽然strcmp()所接收参数不为字符串类型,但最终仍判断其字符串值相同。
demo:
$str1 = "sjjjer";
$str2 = $_GET["str"];
if (strcmp($str1, $str2) == 0) {
echo "两字符串相等";
} else {
echo "两字符串不等";
}
// url: http://192.168.2.106:81/simplectf/SimplectfDemo4.php?str[]=sj
// Warning: strcmp() expects parameter 2 to be string, array given in SimplectfDemo4.php on line 4
// 两字符串相等
可以看到,虽然报错但仍执行了两字符串相等的逻辑。测试版本为php 7.3.4,php 8版本后修复。
in_array() & array_search()
in_array()用于查找指定值是否在数组中,返回true&false。array_search()用于查找指定值的键(索引),若找到返回索引值,若未找到返回false。同样的,这两个数组查找函数,若未指定其第三个值为true(默认为false),则采用松散比较。
demo:
$arr = [0, 19, "sj"];
var_dump(in_array("sjjjer", $arr));
var_dump(array_search("sj", $arr));
var_dump(array_search("sj", $arr, true));
// bool(true)
// int(0)
// int(2)
bool比较
上文案例所产生的有趣结果,主要基于数字、数字字符串、字符串之间的松散比较规则而成。
而接下来则对bool值与字符串的比较规则做一介绍:
若想了解其余类型数据的比较结构,可参考官方比较表:https://www.php.net/manual/en/types.comparisons.php
demo:
// 这里以json_decode() 与 unserialize()函数为例
$str = '{"username": true, "password": true}';
$data = json_decode($str);
var_dump($data->username);
if ($data->username == "admin" && $data->password == "sjjjer") {
echo "身份认证成功!";
} else {
echo "身份认证失败!";
}
// bool(true)
// 身份认证成功!
// true与字符串松散比较结果为true。
// 正确值为:$str = 'a:2:{s:8:"username";s:5:"admin";s:8:"password";s:6:"sjjjer";}';
$str = 'a:2:{s:8:"username";b:1;s:8:"password";b:1;}';
// 其中b表示bool类型,1为true
$data = unserialize($str);
if ($data['username'] == 'admin' && $data['password'] == 'sjjjer') {
echo "身份认证成功!";
} else {
echo "身份认证失败!";
}
// 身份认证成功!
===
以上内容均为开发者采用php松散比较时可能产生的结果,那有没有更为严谨的比较方式呢?采用===
比较,===
在比较时会先比较两边值类型,再比较值,若两边值类型不同也会返回false,也称这种比较方式为严格比较。
demo:
$data1 = "sj";
$data2 = 0;
var_dump($data1 === $data2);
// bool(false)
实验
好了,你现在已经大致了解php的松散比较规则,以及使用松散比较可能会产生的问题了。接下来做几道ctf入门题吧:
第一题
$num = $_GET['num'];
// 判断$num的值是否为数字或数字字符串
if (!is_numeric($num)) {
echo $num;
if ($num == 1) {
echo ' flag!';
}
}
答案:
http://192.168.2.106:81/simplectf/SimplectfDemo8.php?num=1sj
第二题
$md51 = md5('QNKCDZO');
$a = $_GET['a'];
$md52 = md5($a);
// 判断$a是否有值且值不为NULL
if (isset($a)) {
if ($a != 'QNKCDZO' && $md51 == $md52) {
echo "flag";
} else {
echo "false!!!";
}
} else {
echo "please input a";
}
答案:
http://192.168.2.106:81/simplectf/SimplectfDemo9.php?a=000dSb96
至此,本章内容结束!