PHP弱类型产生的安全问题
简介
PHP作为一种弱类型编程语言,在定义变量时无需像C++等强类型时定义数据类型。
<?php
$a = 1;
$a = "hello";
$a = [];
?>
上述代码可以正常运行,在PHP中可以随时将变量改成其他数据类型。下面再来看一个例子:
<?php
$a = '1'; //a现在是字符串'1'
$a *= 2; //a现在是整数2
?>
下面就从具体的实例中总结一下弱类型产生的安全问题。
比较运算符
简介
PHP有这些比较运算符
<?php
$a == $b; //相等
$a === $b; //全等
$a != $b; //不等
$a <> $b; //不等
$a !== $b; //不全等
$a < $b; //小于
$a > $b; //大于
$a <= $b; //小于等于
$a >= $b; //大于等于
$a <==> $b; //太空船运算符:当$a小于、等于、大于$b时分别返回一个小于、等于、大于0的integer 值。 PHP7开始提供.
$a ?? $b ?? $c; //NULL 合并操作符 从左往右第一个存在且不为 NULL 的操作数。如果都没有定义且不为 NULL,则返回 NULL。PHP7开始提供。
?>
存在安全问题的一般都是运算符在类型转换时产生的。给出PHP手册中比较的表格。
下面看一段从PHP手册中摘的一段:
如果该字符串没有包含 ‘.’,’e’ 或 ‘E’ 并且其数字值在整型的范围之内(由 PHP_INT_MAX 所定义),该字符串将被当成 integer 来取值。其它所有情况下都被作为 float 来取值。
<?php
$foo = 1 + "10.5"; // $foo is float (11.5)
$foo = 1 + "-1.3e3"; // $foo is float (-1299)
$foo = 1 + "bob-1.3e3"; // $foo is integer (1)
$foo = 1 + "bob3"; // $foo is integer (1)
$foo = 1 + "10 Small Pigs"; // $foo is integer (11)
$foo = 4 + "10.2 Little Piggies"; // $foo is float (14.2)
$foo = "10.0 pigs " + 1; // $foo is float (11)
$foo = "10.0 pigs " + 1.0; // $foo is float (11)
?>
可以看到字符串中如果有’.’或者e(E),字符串将会被解析为整数或者浮点数。这些特性在处理Hash字符串时会产生一些安全问题。
<?php
"0e132456789"=="0e7124511451155" //true
"0e123456abc"=="0e1dddada" //false
"0e1abc"=="0" //true
"0admin" == "0" //ture
"0x1e240"=="123456" //true
"0x1e240"==123456 //true
"0x1e240"=="1e240" //false
?>
若Hash字符串以0e开头,在进行!=或者==运算时将会被解析为科学记数法,即0的次方。
或字符串为0x开头,将会被当作十六进制的数。
下面给出几个以0e开头的MD5加密之后的密文。
QNKCDZO
0e830400451993494058024219903391
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s155964671a
0e342768416822451524974117254469
s1184209335a
0e072485820392773389523109082030
下面从具体函数理解弱类型带来的问题。
具体函数
md5()
<?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!";
}
}
?>
要求输入的username为字母,password为数字,并且两个变量的MD5必须一样,这时就可以考虑以0e开头的MD5密文。md5(‘240610708’) == md5(‘QNKCDZO’) 可以成功得到flag。
sha1()
<?php
$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>';
?>
sha1函数需要传入的数据类型为字符串类型,如果传入数组类型则会返回NULL
var_dump(sha1([1])); //NULL
因此传入name[]=1&password[]=2 即可成功绕过
strcmp() php version < 5.3
<?php
$password="***************"
if(isset($_POST['password'])){
if (strcmp($_POST['password'], $password) == 0) {
echo "Right!!!login success";n
exit();
} else {
echo "Wrong password..";
}
?>
和sha1()函数一样,strcmp()是字符串处理函数,如果给strcmp()函数传入数组,无论数据是否相等都会返回0,当然这只存在与PHP版本小于5.3之中。
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]);
}
}
?>
intval()函数获取变量的整数值,程序要求从浏览器获得的id值不为1024,因此输入1024.1即可获得flag。
json_decode()
<?php
if (isset($_POST['message'])) {
$message = json_decode($_POST['message']);
$key ="*********";
if ($message->key == $key) {
echo "flag";
}
else {
echo "fail";
}
}
else{
echo "~~~~";
}
?>
需要POST的message中的key值等于程序中的key值,如果传入的key为整数0,那么在比较运算符==的运算下,将会判定字符串为整数0,那么只需要传入数据:message={“key”:0}
array_search() 和 in_array()
上面两个函数和前面d额是一样的问题,会存在下面的一些转换为题:
“admin” == 0; //true
“1admin” == 1; //true
总结
熬夜写文章已经很晚了,就懒得再写总结了。以后每周总结一篇关于代码审计的小知识点。