> For the complete documentation index, see [llms.txt](https://ninjia.gitbook.io/secskill/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ninjia.gitbook.io/secskill/serve/php/md5.md).

# 2.21.4 md5

## md5()

md5()函数使用参考

```php
md5 — 计算字符串的 MD5 散列值

string md5( string $str[, bool $raw_output = false] )


参数 

str
原始字符串。 
raw_output
如果可选的 raw_output 被设置为 TRUE，那么 MD5 报文摘要将以16字节长度的原始二进制格式返回。 


返回值 

以 32 字符十六进制数字形式返回散列值。
```

示例代码：

```php
<?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字节长度的原始二进制格式返回。

先来看下效果：

![](http://ww1.sinaimg.cn/large/0065jC22ly1g171bfy6ctj30kq06e3yj.jpg) ![](http://ww1.sinaimg.cn/large/0065jC22ly1g171bx8ihoj30ld07zwef.jpg)

```php
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�an���l���q��\\' and user=' OR 1=1#'

## 实例

```php
$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 ，我们来看看实际效果。

```
原先：SELECT * FROM admin WHERE username = 'admin' and password = 'md5($password,true)'
变成：SELECT * FROM admin WHERE username = 'admin' and password = ''or'6\xc9]\x99'
```

![](http://ww1.sinaimg.cn/large/0065jC22ly1g171rc1qhxj30mj06a74g.jpg)

由于 and 运算符优先级比 or 高，所以前面的：id=1000 and password = '' 会先执行，然后将执行结果与后面的 '6\xc9]\x99' 进行 or 运算。在布尔运算中，除了 0、'0'、false、null ，其余结果都为真。所以整个 SQL 语句的 where 条件判断部分为真，这样可定就能查出数据。

## md5 碰撞相关问题

代码如下：

```php
<?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开头的，

如

```
md5('240610708') 的结果是：0e462097431906509019562988736854 
md5('QNKCDZO') 的结果是：0e830400451993494058024219903391
```

还有240610708、QNKCDZO、aabg7XSs、aabC9RqS

所以我们传递任意一个进去即可(当为===时不可以)

\===时可以参考

> <https://xz.aliyun.com/t/2232>

## 修复建议

建议在使用 md5 函数的时候，不要将 $raw\_output 字段设置为true 。
