专栏首页离别歌 - 信息安全与代码审计Django中与时区相关的安全问题

Django中与时区相关的安全问题

Django中与时区相关的安全问题

phithon

2020 十月 11 17:53

阅读:224

Python

django, python安全

在开发国际化网站的时候,难免会与时区打交道,通用CMS更是如此,毕竟其潜在用户可能是来自于全球各地的。Django在时区这个问题上下了不少功夫,但是很多资深的开发者都有可能尚未完全屡清楚Django中各种时间的实际意义和使用方法,导致写出错误的代码;作为安全研究人员,时区问题也可能和一些安全问题挂钩,比如优惠券的过期时间、订单的下单与取消时间等,如果没有考虑时区问题,有可能将导致一些逻辑漏洞。

本文就从多个常用模块开始,了解一下Django中的时区究竟是怎么回事,以及在时间的比较中可能出现的一些逻辑错误。

从“两种时间”说起

我们都知道,在Python中表示“时间”的对象是datetime.datetime

其实在Python中,这个对象被分成了两个类型

  • aware datetime
  • naive datetime

他们的区别是:如果datetime对象的tzinfo属性有设置时区值,则这个对象是一个aware datime;否则它是一个naive datetime。

举个例子,我们平时在编写Python脚本的时候,使用下面这行代码获取当前时间:

from datetime import datetime

t = datetime.now()

此时,t是一个naive datetime,因为我们没有给他设置时区:

naive的中文意思大家应该都很熟悉,这里的大概意思就是“simple”,这是一个很简单、原始的时间对象。实际上就是指,计算机不知道这个时间,他的时区究竟是什么,它可能代表着北京时间,也可能是UTC时间,因为我们没有指定时区,我们无法“假设”其是计算机系统所在的时区,也无法“假设”其是UTC时区。也就是说,计算机拿到了一个naive datetime,是无法准确地定位到某一个时间点的,也无法直接转换成一个unix时间戳。

那么相对的,aware datetime就是计算机能准确知道其时区的时间对象,他是一个准确的时间点,就落在时间轴上的某个地方,不管从哪个时区看,这个点都是绝对固定的。所以,我们可以将一个aware datetime转换成unix时间戳。

有的同学可能比较好奇,你说naive datetime无法转换成时间戳,那么为什么这个对象有一个timestamp()方法呢:

原因我们查文档可以得出结论,如果对象是naive datetime,则会以当前系统本地时区为准。

Django的时区配置

回到Django。由于Django是一个国际化框架,时区相关处理自然是其必不可少的组成部分。Django的配置项中,有下面两个选项与时区相关:

  • USE_TZ
  • TIME_ZONE

USE_TZ用来指定整个项目是否使用时区TIME_ZONE是默认时区的值。

如果USE_TZ的值设置为False,那么Django项目中所有时间都使用naive datetime(除非有明确指定时区的情况)。也就是说,网站内存储和使用的时间全部是TIME_ZONE的值所指定的时区。

这样做有一些弊端:

  • 数据库中保存的是naive datetime,导致在跨区域迁移数据的时候,可能无法准确定位到某个时间点
  • 国际化企业可能面向不同国家有不同的网站,但后台数据库相同,此时究竟使用哪个时区保存和展示时间,将引起混乱
  • 即使是同一个网站的用户,他们可能来自于全球各地,查看到的时间却是统一的服务器时间,对于高交互式的应用十分不友好
  • 即使网站面向的用户仅来自于某一个地区,也会涉及到“夏时令”(Daylight Saving Time)相关的问题,每年可能将会导致两次时间误差

默认情况下,用django-admin生成的项目,其设置中USE_TZ等于True,这也是Django官方建议的配置。此时,在网站内部存储与使用的是UTC时间,而与用户交互时使用TIME_ZONE或手工的时区。

我们后文中也以Django的默认配置USE_TZ=True为前提条件,否则也没有讨论的必要了。

Django的时间函数

Django的包django.utils.timezone中有下面几个常用的时间相关函数:

  • now(),返回当前的UTC时间
  • localtime(),返回当前的本地时间(默认是TIME_ZONE配置指定的时区时间)
  • is_aware(),传入的时间是否是aware datetime
  • is_naive(),传入的时间是否是naive datetime
  • make_aware(),将naive时间转换成aware时间
  • make_naive(),将aware时间转换成naive时间

因为开启了USE_TZ,Django内部操作时间时都应该使用aware时间,否则会出现异常。所以,我们在获取当前时间的时候,一定要使用Django自带的now()localtime()函数,而不能使用Python的datetime.datetime.now()函数。

数据库存储的时间

我们在使用ORM的DatetimeField时,常常会有这样的疑虑:我们究竟应该给DatetimeField传入哪个时区的时间呢?

可以做个试验,编写下面这个model:

class Archive(models.Model):
    title = models.CharField('title', max_length=256)

    now_time = models.DateTimeField(default=timezone.now)
    local_time = models.DateTimeField(default=timezone.localtime)

这个model有三个属性,title是他的名字,now_time和local_time是两个时间,他们的默认值分别是timezone.now和timezone.localtime。

也就是说,默认情况下,now_time字段传入的是UTC时区的当前时间,local_time字段传入的是本地时区的当前时间,我这里是Asia/Shanghai

然后,我们创建一个Archive对象:

可以发现,不管我们使用a.now_time还是a.local_time,读取到的datetime对象的tzinfo都是UTC。

这也印证了Django文档中说到的,不管传入的时间对象时区是什么,其内部存储的时间均为UTC时区。但是,值得注意的是,如果我们传入了一个不带时区的naive datetime,将会出现一个警告,并使用默认时区填充其tzinfo:

模板中展示的时间

对于网站的用户来说,他们想看到的时间显然不是UTC时间,而是某一个具体时区的时间。比如,我的网站几乎全部是中国用户,那么展示时使用的时区应该是Asia/Shanghai

这一部分的转换,Django放在的模板引擎中。

Django在渲染模板变量时,将会遇到两种与时间有关的情况:

<p>origin value: {{ object.now_time }}</p>
<p>date filter: {{ object.now_time | date:'Y-m-d H:i:s' }}</p>

前者是直接将时间渲染到页面中,后者是通过date这样的模板filter处理后渲染在页面中。这两种情况在内部处理方式略有不同此处不细表,总体而言,任意模板中变量的渲染,都会被转换时区。

那么,脱离模板引擎,我们会得到怎样的结果呢?

在流行的前后端分离架构中,后端服务器通常只提供JSON格式的接口给前端,那么,我们编写下面这样一个view,看看返回值是什么:

from django.shortcuts import get_object_or_404
from django.http.response import JsonResponse
from django.utils import timezone

from . import models


def json(request):
    object = get_object_or_404(models.Archive, pk=1)
    data = dict(
        id=object.pk,
        now_time=object.now_time,
        local_time=timezone.localtime(object.local_time)
    )
    return JsonResponse(data=data)

返回对象的now_time,我直接将object.now_time返回;返回对象的local_time,我将数据库值转换成本地时间timezone.localtime(object.local_time)返回。

我前文说过,这两个值在数据库中的值是完全相等的,不过在json返回中,now_time是UTC时间,而local_time是北京时间:

也就是说,在前后端分离的网站中,如果直接使用Model的字段,那么前端需要负责进行时区的转换,否则将会出现时间的偏差。

时间的校验和比较

在一些业务场景下,我们可能会涉及到时间的校验和比较,如:

  • 付费服务、商品、用户的有效期检查
  • 活动的开始与结束时间检查
  • 订单、商品的收货、取消时间检查

我们就以付费用户为例:用户购买了30天的VIP会员,我们需要给用户表中设置一个过期时间,比如下面这个model。

from django.db import models
from django.utils import timezone

class Account(models.Model):
    username = models.CharField(max_length=256)
    password = models.CharField(max_length=64)

    created_time = models.DateTimeField(default=timezone.now)
    expired_time = models.DateTimeField()

如果某个用户某一个时刻对网站进行访问,我们如何判断他是否具有VIP权限呢?

通常情况下我们有两种常见的判断方法。一是,用户访问时,直接从model中取出这个对象,然后和now()进行比较:

这种情况下,当前时间不管是now()还是localtime()都不影响比较的结果,因为两个datetime对象在比较时会考虑时差。

另一种情况是,通过ORM的queryset进行比较,等于在数据库层面进行操作:

if models.Account.objects.filter(expired_time__gt=timezone.now()).exists():
    # doing sth

Django也帮我们考虑过这种情况,即使此时我们使用本地时间timezone.localtime()进行查询,系统也会将其转换成UTC时间传入SQL语句:

但是,如果我们使用到了和日期、时间有关的lookups,将产生相反的结果。

怎么理解这个问题呢,我们还是来举个例子。比如,网站以用户注册当天的日子作为“会员日”(比如1月2日注册的会员,以后每月的2日都是他的会员日),会员日这一天会给这个用户赠送优惠券。

那么,发送优惠券时,我们如何筛选网站内会员日是今日的所有用户?

下面这个filter是否正确?

models.Account.objects.filter(created_time__day=timezone.now().day).all()

答案是否定的,我们应该使用timezone.localtime()表示今天,而非timezone.now()

models.Account.objects.filter(created_time__day=timezone.localtime().day).all()

这是为什么呢?你不是说数据库中存储的都是UTC时间吗,为何会使用到timezone.localtime()

原因是,Django在使用日期、时间有关的lookups时,会在数据库层面对时间进行时区的转换再进行比较,所以我们需要使用本地时间而不是UTC时间。

可以看看原始的SQL语句:

可见,SQL语句中使用了django_datetime_extract('day', "sample_account"."created_time", 'Asia/Shanghai', 'UTC')将UTC时间转换成了北京时间,因此后面比较的时候,也应该使用北京时间。

这一点需要格外注意。时间比较的不谨慎,说小点是一个Bug,说大点就是漏洞,毕竟很多涉及到时间比较的情景,都是非常需要严谨的。

所以,我们总结一下:

  • 任何比较都使用aware时间,不能使用naive时间
  • 时间属性直接比较时,使用任何aware时间均可(会被自动转换成UTC)
  • queryset查询,不涉及__day__date__year等时间lookups时,使用任何aware时间均可(会被自动转换成UTC)
  • queryset查询,涉及到时间lookups时,使用本地时间

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 与访问控制相关的安全问题

    学习打卡计划是信安之路知识星球开启的 “每天读书一小时,挑战打卡一百天” 主题活动,能够坚持学习打卡 100 天的同学可以获得信安之路提供的百分成就徽章和证书,...

    信安之路
  • kali w3af的安装与相关问题

         kali 最新版本的安装好后是没有w3af的。(官方网站下载的2017最新版),(2016版的是有w3af的)。下面就开始安装w3af。

    py3study
  • 针对PHP开发安全问题的相关总结

    对于互联网应用的开发,作为开发者必须时刻牢记安全观念,并在开发的代码中体现。PHP脚本语言对安全问题并不太关心,特别是对大多数没有经验的开发者来说。每当你做任何...

    砸漏
  • 针对PHP开发安全问题的相关总结

    对于互联网应用的开发,作为开发者必须时刻牢记安全观念,并在开发的代码中体现。PHP脚本语言对安全问题并不太关心,特别是对大多数没有经验的开发者来说。每当你做任何...

    用户8664418
  • Django 博客教程:前言和环境安装(连载一)

    專 欄 ❈追梦人物,电子科技大学计算机学院研究生,从事大数据分析研究方向。主要使用 Python 语言进行相关数据的分析,熟练使用 django 开发网站系统。...

    Python中文社区
  • 内网渗透 | Kerberos 协议相关安全问题分析与利用

    在上一节中我们说到过,Kerberos 认证并不是天衣无缝的,这其中也会有各种漏洞能够被我们利用,比如我们常说的 MS14-068、黄金票据、白银票据等就是基于...

    HACK学习
  • 关于helm安装jenkins后时区问题的强迫症

    helm 安装了jenkins构建应用的使用不知道有没有跟我出现同样问题的小伙伴:

    对你无可奈何
  • Python OpenCV与中文相关的三个常见问题

    --------------------------------------------------------------------------

    Color Space
  • Django博客教程(搭建开发环境)

    本教程写作时开发环境的系统平台为 Windows 10 (64 位),Python 版本为 3.6(64 位),Django 版本为 2.0。

    HUBU生信
  • 搭建开发环境

    本教程使用的开发环境 本教程写作时开发环境的系统平台为 Windows 10 (64 位),Python 版本为 3.5.2 (64 位),Django 版本为...

    追梦人物
  • 关于容器中镜像构建的安全问题

    确保容器中服务与应用安全是容器化演进的关键点。容器安全涉及到应用开发与维护的整个生命周期,本文主要从镜像构建的视角来看docker容器的一些安全问题及应对措施。

    公众号 容器云实践
  • Django教程(一)- Django视图与网址1.简介2.环境搭建3.安装pycharm4.Ubuntu下 正确安装VMware Tools5.Django主要模块6.Django基本命令7. Dj

    Django 是用Python开发的一个免费开源的Web框架,可以用于快速搭建高性能,优雅的网站!

    Python攻城狮
  • 如何在Ubuntu 14.04上使用MySQL或MariaDB和Django应用程序

    Django是一个用于快速创建Python应用程序的灵活框架。默认情况下,Django应用程序配置为将数据存储到轻量级SQLite数据库文件中。虽然这在某些负载...

    苏子晨
  • Django Haystack 全文检索与关键词高亮

    在此之前我们使用了 Django 内置的一些方法实现了一个简单的搜索功能。但这个搜索功能实在过于简单,没有多大的实用性。对于一个搜索引擎来说,至少应该能够根据用...

    追梦人物
  • 生产环境使用Nginx+uwsgi部署Django

    马哥linux运维 | 最专业的linux培训机构 ---- 在本地运行django应用相对来说还是挺方便的,使用自带的runserver启动即可。如果在生...

    小小科
  • 小白python建站的一些准备(三)

    在介绍ORM之前,先介绍一下Django操作Mysql数据库的常用方法,因为我只是对Mysql数据库比较了解,所以其它的Oracle等数据库不做介绍,在...

    文科生的python自学之路
  • 如何在Debian 9上使用Postgres,Nginx和Gunicorn设置Django

    Django是一个功能强大的Web框架,可以帮助您实现Python应用程序或网站。Django包含一个简化的开发服务器,用于在本地测试您的代码,但是对于任何与生...

    大瓜皮
  • 都2021年了还不会在CentOS7上使用pyenv搭建Django环境吗?

    pyenv是一个forked自ruby社区的简单、低调、遵循UNIX哲学的Python环境管理工具, 它可以轻松切换全局解释器版本, 同时结合vitualenv...

    查理不是猹
  • Django---时间的时区问题

      在用django1.8版本做项目的时候遇到时间的存储与读取不一致的问题,网上找了很多帖子,但都没有讲明白。本文将在项目中遇到的问题及如何解决的尽可能详细的记...

    用户1214487

扫码关注云+社区

领取腾讯云代金券