md5()
md5()函数使用参考
Copy md5 — 计算字符串的 MD5 散列值
string md5 ( string $str[ , bool $raw_output = false ] )
参数
str
原始字符串。
raw_output
如果可选的 raw_output 被设置为 TRUE ,那么 MD5 报文摘要将以16字节长度的原始二进制格式返回。
返回值
以 32 字符十六进制数字形式返回散列值。
示例代码:
Copy <? php
error_reporting ( 0 ) ;
$flag = 'flag{test}' ;
if ( isset ( $_GET[ 'username' ] ) and isset ( $_GET[ 'password' ] ) ) {
if ($_GET[ 'username' ] == $_GET[ 'password' ])
print 'Your password can not be your username.' ;
else if ( md5 ( $_GET[ 'username' ] ) === md5 ( $_GET[ 'password' ] ) )
die ( 'Flag: ' . $flag);
else
print 'Invalid password' ;
}
?>
代码审计需要满足两个条件:1. username和password的值不能相同
2. username和password的MD5值相同 利用MD5函数不能处理数组进行构造payload
http://123.206.87.240:9009/18.php?username[]=6&password[]=2
Raw MD5 Hash引发的注入
根据描述
如果可选的 raw_output 被设置为 TRUE,那么 MD5 报文摘要将以16字节长度的原始二进制格式返回。
先来看下效果:
Copy class LoginManager {
private $em;
private $user;
private $password;
public function __construct ($user , $password) {
$this -> em = DoctrineManager :: getEntityManager () ;
$this -> user = $user;
$this -> password = $password;
}
public function isValid () {
$user = $this -> sanitizeInput ( $this -> user ) ;
$pass = $this -> sanitizeInput ( $this -> password ) ;
$queryBuilder = $this -> em -> createQueryBuilder ()
-> select ( "COUNT(p)" )
-> from ( "User" , "u" )
-> where ( "user = '$user' AND password = '$pass'" ) ;
$query = $queryBuilder -> getQuery () ;
return boolval ( $query -> getSingleScalarResult ()) ;
}
public function sanitizeInput ($input , $length = 20 ) {
$input = addslashes ( $input ) ;
if ( strlen ( $input ) > $length) {
$input = substr ( $input , 0 , $length ) ;
}
return $input;
}
}
$auth = new LoginManager ($_POST[ 'user' ] , $_POST[ 'passwd' ]);
if ( ! $auth -> isValid () ) {
exit ;
}
从 第17行-20行 代码中明显存在SQL语句拼接的形式,而 $pass 变量和 $user 变量是在 第30-31行 中通过 POST 方式由用户进行控制。这里很明显存在SQL注入漏洞
程序代码 第14行 调用 sanitizeInput 函数针对用户输入的 $user 变量进行了处理,跟进一下 sanitizeInput 函数,在 第25行 找到这个函数,这个函数的作用就是调用 addslashes 函数针对输入数据进行处理。
所以按照这种情况下这个地方,似乎不存在注入点了,先别急,我们继续往下看,我们看到 第13行 代码针对用户输入 password 的值调用 md5 函数进行相关处理
我们知道我可以控制的点有两个变量,一个是 $user ,一个是 $pass ,$pass 经过了 md5 的处理,但是返回字段不是标准的md5值,$user 经过了 addslashes 函数的处理,无法引入特殊符号去闭合。这里做个假设,如果我们经过 $pass = md5($this->password, true); 处理之后的值逃逸出一个反斜杆,那么实际上带入到数据库的值就如下所示:
select count(p) from user s where password='xxxxxx\' and user='xxx#'
如果这种情况发生,实际上也存在了SQL注入。我们尝试fuzz一下,看看会不会存在某个值经过了 md5(xxx, true) 处理之后,最后一位是反斜杠。
我们针对1-1000进行一下fuzz,发现 md5(128, true) 最后的结果带有反斜杠。因此这题最后的payload如下:
user= OR 1=1#&passwd=128
带入到数据库查询的语句如下:
select count(p) from user s where password='v�an���l���q��\' and user=' OR 1=1#'
实例
Copy $password = $_POST[ 'password' ];
$sql = " SELECT * FROM admin WHERE username = 'admin' and password = '" . md5 ( $password , true ) . "'" ;
$result = mysqli_query ( $link , $sql ) ;
if ( mysqli_num_rows ( $result ) > 0 ){
echo 'flag is :' . $flag;
}
else {
echo '密码错误!' ;
}
从上图中的代码中的 第5行 可以看到,当查询结果返回大于0的时候,就会输出 flag ,我们前面分析过当 md5 函数的 $raw_output 设置会true的时候, md5 函数返回前16字节长度的原始二进制,然后再将二进制转换成字符串,这种情况下可能会引入单引号等特殊字符。
有人尝试过破解这个类型的字符,目前已知两个是 ffifdyop 和129581926211651571912466741651878684928 ,我们来看看实际效果。
Copy 原先:SELECT * FROM admin WHERE username = 'admin' and password = 'md5($password,true)'
变成:SELECT * FROM admin WHERE username = 'admin' and password = ''or'6\xc9]\x99'
由于 and 运算符优先级比 or 高,所以前面的:id=1000 and password = '' 会先执行,然后将执行结果与后面的 '6\xc9]\x99' 进行 or 运算。在布尔运算中,除了 0、'0'、false、null ,其余结果都为真。所以整个 SQL 语句的 where 条件判断部分为真,这样可定就能查出数据。
md5 碰撞相关问题
代码如下:
Copy <? php
$md51 = md5 ( 'QNKCDZO' ) ;
$a = @ $_GET[ 'a' ];
$md52 = @ md5 ( $a ) ;
if ( isset ( $a ) ){
if ($a != 'QNKCDZO' && $md51 == $md52) {
echo "flag{*}" ;
} else {
echo "false!!!" ;
}}
else { echo "please input a" ;}
?>
思路很明确,a的值不能等于QNKCDZO但是a经过md5后的值必须与QNKCDZO的值相等
因为php 0e开头的字符串都是==的,不是===
如:
var_dump("0e462097431906854"=="0e83040041");
上面这个返回true,如果题目的md5是0e开头的,
如
Copy md5('240610708') 的结果是:0e462097431906509019562988736854
md5('QNKCDZO') 的结果是:0e830400451993494058024219903391
还有240610708、QNKCDZO、aabg7XSs、aabC9RqS
所以我们传递任意一个进去即可(当为===时不可以)
===时可以参考
https://xz.aliyun.com/t/2232
修复建议
建议在使用 md5 函数的时候,不要将 $raw_output 字段设置为true 。