文章851
标签121
分类10

PHP 反序列化注入踩坑

运气真不错 牛市来了 服务就让人盯上了 教训很深刻 50w 的学费.

服务代买使用 php CI框架 写的. 最近线上服务器出现错误

[2019-05-15 22:03:21]127.0.0.1
content: [15-May-2019 22:03:21 Asia/Shanghai] PHP Fatal error: DISEVAL - Use of eval is forbidden in /data/release/project/system/View/Parser.php(526) : eval()'d code on line 1
[2019-05-15 22:03:21]127.0.0.1
content: [15-May-2019 22:03:21 Asia/Shanghai] PHP Stack trace:
[2019-05-15 22:03:21]127.0.0.1
content: [15-May-2019 22:03:21 Asia/Shanghai] PHP 1. {main}() /data/release/project/public/index.php:0
[2019-05-15 22:03:21]127.0.0.1
content: [15-May-2019 22:03:21 Asia/Shanghai] PHP 2. CodeIgniter\CodeIgniter->run() /data/release/project/public/index.php:46
[2019-05-15 22:03:21]127.0.0.1
content: [15-May-2019 22:03:21 Asia/Shanghai] PHP 3. CodeIgniter\CodeIgniter->handleRequest() /data/release/project/system/CodeIgniter.php:203
[2019-05-15 22:03:21]127.0.0.1
content: [15-May-2019 22:03:21 Asia/Shanghai] PHP 4. CodeIgniter\CodeIgniter->runController() /data/release/project/system/CodeIgniter.php:248
[2019-05-15 22:03:21]127.0.0.1
content: [15-May-2019 22:03:21 Asia/Shanghai] PHP 5. App\Controllers\Member->emailActivate() /data/release/project/system/CodeIgniter.php:713

运维同学通过 nginx 日志及logtail 最后定位到 一个对外的api接口. 该接口uri中参数通过 base64 加密并通过 serialize 函数序列化.
服务端接收参数后 解base64 然后在使用 unserialize 反序列化.
正常来看实际没什么问题. 但 由于反序列化的特性 unserialize — 从已存储的表示中创建 PHP 的值 这个php值 可以杀死对象.

经过测试 反序列化可以在已知条件下调用所有项目中存在的所有方法.

黑客 发现漏洞后 通过大量测试 , 得知当前框架, 直接就可以开始行动了

经过观察 近一周的试探及数据收集 在凌晨黑客开始对服务器进程数据篡改.

问题查找比较费劲 主要问题在于 反序列化的数据解出来可能是 php对象 真实厉害呀 会触发析构及其他魔术方法.

举个栗子 :

   // 序列化对象 Ci 默认数据库配置
    public function se()
    {
        $class = new Config/Database();
        $classStr = serialize($class);
        echo base64_encode($classStr);
    }

    // 解包
    public function uns()
    {
        $a = $_POST['a'];
        try {
            $ser =$a;

        $class = unserialize( base64_decode($ser));
        }catch (\Exception $e){
            var_dump($e->getMessage());
        }

        var_dump($class);
    }

总结下:
线上禁用 unserialize 方法, 防止反序列化注入, 形成webshell
通过反序列化 调用 Ci 生成模板缓存的方法, 实现消息回包. 实际上代码以上可以全部获取 接收回包的方式

PHP 身份证号码验证方法

在 PHP 中,验证中国身份证号码的有效性是一个常见需求。身份证号码通常为 15 位或 18 位。以下是验证身份证号码的详细步骤和示例代码。

1. 身份证号码的基本规则

  • 15 位身份证:由 15 位数字组成,通常是老旧的身份证格式。
  • 18 位身份证:由 17 位数字和 1 位校验码组成,校验码可以是数字或字母 X(表示 10)。

校验规则

  1. 长度检查:身份证号码必须是 15 位或 18 位。
  2. 数字格式检查:身份证号码的前 17 位必须是数字,最后一位可以是 X。
  3. 地区码检查:前 6 位是地区码,必须是有效的地区编码。
  4. 出生日期检查:需要检查身份证中包含的出生日期是否有效。
  5. 校验位计算(仅适用于 18 位身份证)。

2. 示例代码

以下是一个验证身份证号码的 PHP 函数示例:

<?php

function isValidIdCard($idCard) {
    // 1. 检查长度
    $idCard = trim($idCard);
    if (strlen($idCard) != 15 && strlen($idCard) != 18) {
        return false;
    }

    // 2. 检查数字格式
    if (strlen($idCard) == 15) {
        if (!preg_match('/^\d{15}$/', $idCard)) {
            return false;
        }
    } else {
        if (!preg_match('/^\d{17}[\dX]$/i', $idCard)) {
            return false;
        }
        
        // 3. 校验位计算
        if (!checkIdCardChecksum($idCard)) {
            return false;
        }
    }

    // 4. 检查出生日期
    return checkBirthday(substr($idCard, 6, 8));
}

function checkIdCardChecksum($idCard) {
    $weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3];
    $sum = 0;
    for ($i = 0; $i < 17; $i++) {
        $sum += intval($idCard[$i]) * $weights[$i];
    }
    $checkDigit = (12 - ($sum % 11)) % 11;
    $checkDigit = $checkDigit == 10 ? 'X' : $checkDigit;
    
    return strtoupper($idCard[17]) == strtoupper($checkDigit);
}

function checkBirthday($birthday) {
    $year = substr($birthday, 0, 4);
    $month = substr($birthday, 4, 2);
    $day = substr($birthday, 6, 2);
    
    // 检查日期合法性
    if (!checkdate($month, $day, $year)) {
        return false;
    }
    
    // 检查出生日期是否在合理范围内
    $birthDate = DateTime::createFromFormat('Ymd', $birthday);
    return $birthDate <= new DateTime();
}

// 测试
$idCard = '11010519491231002X'; // 示例身份证号码
if (isValidIdCard($idCard)) {
    echo "身份证号码有效。\n";
} else {
    echo "身份证号码无效。\n";
}
?>

3. 代码说明

  • isValidIdCard:主函数,检查身份证号码的有效性。
  • checkIdCardChecksum:计算校验位并与身份证末尾进行比较。
  • checkBirthday:检查出生日期的合法性,包括日期范围和格式。

4. 注意事项

  • 确保输入的身份证号码是字符串类型,并去除两端空格。
  • 处理身份证号码时要注意数据安全和隐私保护。

PHP 毫秒级时间戳与日期格式互换

在 PHP 中,处理时间戳和日期格式的转换是常见的需求。下面将介绍如何在毫秒级时间戳和日期格式之间进行互换。

1. 获取当前时间的毫秒级时间戳

可以使用 microtime() 函数获取当前时间的微秒时间戳,然后将其转换为毫秒级时间戳。

示例代码

// 获取当前时间的毫秒级时间戳
$milliseconds = round(microtime(true) * 1000);
echo "当前毫秒级时间戳: " . $milliseconds . "\n";

2. 毫秒级时间戳转换为日期格式

要将毫秒级时间戳转换为日期格式,可以使用 DateTime 类或 date() 函数。

使用 DateTime

$milliseconds = 1633036800000; // 示例毫秒级时间戳
$datetime = DateTime::createFromFormat('U.u', sprintf('%d.%03d', floor($milliseconds / 1000), $milliseconds % 1000));
echo "日期格式: " . $datetime->format('Y-m-d H:i:s.u') . "\n"; // 输出:日期格式: 2021-10-01 00:00:00.000

使用 date() 函数

$milliseconds = 1633036800000; // 示例毫秒级时间戳
$seconds = $milliseconds / 1000; // 转换为秒
$date = date('Y-m-d H:i:s', $seconds);
echo "日期格式: " . $date . "\n"; // 输出:日期格式: 2021-10-01 00:00:00

3. 日期格式转换为毫秒级时间戳

要将日期格式转换为毫秒级时间戳,可以使用 strtotime() 函数获取秒级时间戳,然后乘以 1000。

示例代码

$dateString = '2021-10-01 00:00:00'; // 示例日期格式
$timestamp = strtotime($dateString); // 获取秒级时间戳
$milliseconds = $timestamp * 1000; // 转换为毫秒级时间戳
echo "毫秒级时间戳: " . $milliseconds . "\n"; // 输出:毫秒级时间戳: 1633036800000

4. 综合示例

以下是一个完整的示例,演示了如何在毫秒级时间戳和日期格式之间进行互换:

<?php
// 获取当前时间的毫秒级时间戳
$milliseconds = round(microtime(true) * 1000);
echo "当前毫秒级时间戳: " . $milliseconds . "\n";

// 毫秒级时间戳转换为日期格式
$datetime = DateTime::createFromFormat('U.u', sprintf('%d.%03d', floor($milliseconds / 1000), $milliseconds % 1000));
echo "日期格式: " . $datetime->format('Y-m-d H:i:s.u') . "\n";

// 日期格式转换为毫秒级时间戳
$dateString = '2021-10-01 00:00:00'; // 示例日期格式
$timestamp = strtotime($dateString);
$milliseconds = $timestamp * 1000;
echo "毫秒级时间戳: " . $milliseconds . "\n"; // 输出:毫秒级时间戳: 1633036800000
?>
">