python安全问题
老铁233 发表于 广东 WEB安全 754浏览 · 2024-09-16 14:30

python安全问题

简介

​ 这篇文章中,记录一些python 的安全问题

环境记录

​ 具体demo代码见下文

  • python2
  • python3
  • pyenv

从python开始

Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。

Python 的设计具有很强的可读性,相比其他语言经常使用英文关键字,其他语言的一些标点符号,它具有比其他语言更有特色语法结构

  • Python 是一种解释型语言: 这意味着开发过程中没有了编译这个环节。类似于PHP和Perl语言
  • Python 是交互式语言: 这意味着,您可以在一个 Python 提示符 >>> 后直接执行代码
  • Python 是面向对象语言: 这意味着Python支持面向对象的风格或代码封装在对象的编程技术
  • Python 是应用广泛的语言:支持广泛的应用程序开发,从简单的文字处理到 WWW 浏览器再到游戏

安全问题

断言问题

在 Python 中,assert 语句可以用于调试时的检查,但在安全性方面存在一些隐患,特别是在生产环境中使用时。以下是常见的 assert 安全问题:

  1. 运行时被忽略
    • assert 语句在 Python 中只在调试模式下有效。如果使用了优化模式 (python -O) 运行 Python 代码,所有的 assert 语句都会被跳过。这意味着在生产环境中,assert 可能不会被执行,从而跳过关键的检查,可能导致意外的安全漏洞。
    • 优化模式会使代码运行更快且占用内存更少
  2. 不适用于异常处理
    • assert 主要用于开发过程中的检查,不能替代正式的错误处理。依赖 assert 来验证输入或执行关键逻辑检查是不安全的。在处理外部输入、权限检查或其他安全相关逻辑时,应该使用标准的异常处理机制(如 if 语句和 try-except 块)。
  3. 敏感信息泄露
    • assert 语句中可能包含敏感信息,如内部状态、系统路径或错误消息。在调试模式下,这些信息可能会被暴露在日志或错误报告中,攻击者可以利用这些信息进行进一步攻击。

安全替代方案

  1. 使用条件和异常

    • 在关键代码路径中,使用显式的条件检查和异常处理,而不是 assert。例如:

      if not condition:
          raise ValueError("Invalid condition")
      
  2. 日志记录与监控

    • 通过安全的日志记录机制代替 assert 语句,避免敏感信息暴露的同时提供足够的调试信息。
  3. 输入验证

    • 对外部输入进行严格的验证,避免依赖 assert 进行输入验证。

总体来说,assert 更适合开发阶段调试,在生产环境下使用时,应该替换为更严谨的异常处理机制。

一个demo

def authenticate_user(user, password):
    # 在生产环境中,assert 可能会被忽略,导致未进行身份验证检查
    assert user == "admin" and password == "secure_password", "Authentication failed!"
    print(f"Welcome, {user}!")

# 示例:错误的使用 assert 进行身份验证
user_input = input("Enter username: ")
password_input = input("Enter password: ")

authenticate_user(user_input, password_input)

创建文件夹

​ 在 Python 中使用 os.makedirs()os.mkdir() 创建目录时,如果不正确设置目录权限,可能导致一系列安全问题。尤其是在多用户系统或处理敏感数据时,默认或不安全的权限设置会让目录暴露于潜在的攻击风险中。

权限问题

  • demo.py
import os

def create_dir(dir_path):

    os.makedirs(dir_path, mode=0o700)

create_dir("a/b/c")
  • 当python > 3.6 文件夹权限分别为
    • a 0x755
    • b 0x755
    • c 0x700
  • 当python < 3.6 (测试版本为2.7) 文件夹权限分别为
    • aa 0x700
    • b 0x700
    • c 0x700

  • 在 Python > 3.6 中,函数os.makedirs具有与 Linux 命令相同的属性: mkdir -m 700 -p A/B/C

条件竞争

​ 在某些情况下,目录创建时没有立即设置权限,后续通过 os.chmod() 修改权限。这会产生一个时间窗口,在这个窗口内,存在潜在的风险利用可能

import os

dir_path = "/tmp/sensitive_data"
os.makedirs(dir_path)

xxxxx
xxxxx
os.chmod(dir_path, 0o700)  # 事后设置权限

不安全的路径

如果创建目录时没有正确验证路径,攻击者可以通过传入恶意路径(如 ../../),在不受控的路径下创建目录,可能导致系统被破坏或数据被篡改

import os

def create_user_dir(username):
    dir_path = f"/home/{username}/data"
    os.makedirs(dir_path)

# 如果 username 是 "../../etc", 那么目录将被创建在 /etc 下
create_user_dir("../../etc")

路径拼接

​ 主要为 os.path.join函数的不规范使用

路径遍历攻击

​ 如果用户提供的输入包含恶意构造的路径,比如通过 ../(父目录路径),攻击者可以使用这种输入绕过目录限制,访问系统中其他敏感目录。

  • demo.py
import os

def save_file(upload_dir, filename):
    # 使用 os.path.join 拼接路径
    file_path = os.path.join(upload_dir, filename)
    with open(file_path, 'w') as f:
        f.write("This is a test file.")

# 假设上传目录是 /uploads
save_file("/uploads", "../../etc/passwd")

符号链接攻击(Symlink Attack)

​ 攻击者可能创建符号链接指向敏感的系统文件,而应用程序可能错误地信任这些符号链接,并在这些路径下写入或读取数据,从而导致安全问题(这部分攻击场景相对比较理想化,只提供思路)

绝对路径覆盖

​ 如果输入的路径是一个绝对路径(以 / 开头),os.path.join() 会忽略前面的路径部分,直接返回这个绝对路径。攻击者可以利用这种行为覆盖指定的文件路径

import os

# 如果 filename 是一个绝对路径
file_path = os.path.join("/safe/directory", "/etc/passwd")
print(file_path)  # 输出: /etc/passwd

临时文件问题

tempfile.NamedTemporaryFile函数用于创建具有特定名称的临时文件。其中, prefixsuffix参数容易受到路径遍历攻击

import tempfile
import os

id = "/../tmp/test"
tmp_file = tempfile.NamedTemporaryFile(prefix=id, delete=False)
file_name = tmp_file.name
tmp_file.close()

用户输入的id用作临时文件的前缀。如果攻击者将有效负载/../var/www/test作为id参数传递,则会创建以下tmp文件: /var/www/test_zdllj17 。可能存在风险较高的组合漏洞

Extended Zip Slip

Extended Zip Slip 是一种路径遍历攻击,涉及将压缩文件(如 .zip.tar.gz 等)解压缩时利用恶意构造的文件名,突破预期的文件目录限制,进而覆盖或创建系统中不应访问的文件。攻击者可能通过构造恶意的路径,实现远程代码执行(RCE)或覆盖系统中的关键文件。Python 在处理压缩文件时,如果不进行安全验证,也可能遭遇此类攻击。

​ 当一个压缩包中包含恶意构造的文件路径(例如 ../../etc/passwd),解压程序如果没有适当的检查,可能会将这些路径视为相对路径,从而将文件解压到系统的敏感目录。这种攻击称为路径遍历(Directory Traversal)。

  • demo1
import zipfile
import os
# ../../etc/passwd attack
def extract_zip(zip_file, extract_to):
    with zipfile.ZipFile(zip_file, 'r') as zf:
        zf.extractall(extract_to)  # 直接解压所有文件
  • demo2
# 即使做了 后缀检验  防不住 ../../../var/www/html
def extract_html(request):
    filename = request.FILES["filename"]
    zf = zipfile.ZipFile(filename.temporary_file_path(), "r")
    for entry in zf.namelist():
        if entry.endswith(".html"):
            file_content = zf.read(entry)
            with open(entry, "wb") as fp:
                fp.write(file_content)
    zf.close()
    return HttpResponse("HTML files extracted!")

正则绕过

  • re.search:扫描整个字符串,直到找到第一个符合正则表达式的匹配项
  • re.match
    • re.match 仅从字符串开头开始匹配,不会匹配换行符
    • 使用 re.MULTILINE 标志可以让 ^$ 匹配每行的开头和结尾
    • 使用 re.DOTALL 标志可以让 . 匹配包括新行在内的所有字符
import re

def is_sql_injection(test_str):
    pattern = re.compile(r".*(union)|(select).*")
    if re.search(pattern, test_str):
        return True
    return False

def is_sql_injection_match(test_str):
    pattern = re.compile(r".*(union)|(select).*")
    if re.match(pattern, test_str):
        return True
    return False


def is_sql_injection_match_fix(test_str):
    pattern = r".*(union)|(select).*"
    if re.match(pattern, test_str, re.DOTALL):
        return True
    return False

test_a = "union xxxasd"
test_b = "xxxasd \n\
union ASDSDFF"

print(is_sql_injection(test_a))
print(is_sql_injection(test_b))

print(is_sql_injection_match(test_a))
print(is_sql_injection_match(test_b))

print(is_sql_injection_match_fix(test_a))
print(is_sql_injection_match_fix(test_b))

>>>
[root@localhost python_sec]# python3 demo5.py 
True
True
True
False
True
True

备注:re.DOTALL后不可使用预编译compile,会导致性能有损

编码的问题

​ 不同编码规则的差异会导致一些绕过问题,比如漏洞防御代码绕过等。这里拿unicode进行举例

  • unicode编码绕过过滤
import unicodedata
from django.shortcuts import render
from django.utils.html import escape

def render_input(request):
    user_input = escape(request.GET["p"])
    normalized_user_input = unicodedata.normalize("NFKC", user_input)
    context = {"my_input": normalized_user_input}
    return render(request, "test.html", context)

demo十分简单,先进行escape后unicodedata.normalize解码,那么

%EF%B9%A4 -> <
%EF%B9%A5 -> <

利用unicode编码成功绕过xss防御,将恶意payload注入到render函数执行

  • unicode大小写问题
from django.core.mail import send_mail
from django.http import HttpResponse
from vuln.models import User

def reset_pw(request):
    email = request.GET["email"]
    result = User.objects.filter(email__exact=email.upper()).first()
    if not result:
        return HttpResponse("User not found!")
    send_mail(
        "Reset Password",
        "Your new pw: 123456.",
        "from@example.com",
        [email],
        fail_silently=False,
    )
    return HttpResponse("Password reset email sent!")

demo问题点出在 先super操作,很多字符大写后会被兼容转换

foo@mıx.com -> FOO@MIX.COM
foo@mix.com -> FOO@MIX.COM

攻击者可以利用上述绕过手法进行绕过

反序列化

​ python的反序列化分为 pickle 和 pyyaml两种,这里不做赘述

pip相关

​ Python 的 pip 是 Python 包管理工具,负责安装和管理 Python 包。随着供应链攻击成为网络安全的一个重要问题,确保 pip 生态系统的安全性变得尤为重要

  • 使用可信的源:指定官方源:优先使用官方的 Python 包索引(PyPI)源 (pypi.org/simple)。避免使用不可信的第三方源。
pip install --index-url=https://pypi.org/simple <package>
  • 验证包的完整性
pip install --require-hashes -r requirements.txt
  • requirement指定hash值
package==1.0.0 --hash=sha256:hashvalue
  • 更新操作:这部分建议非必要不更新,防止黑客投毒

  • ...etc等

写在最后

​ 持续整理一些python的相关安全问题

参考

https://deepsource.com/blog/python-security-pitfalls

https://www.sonarsource.com/blog/10-unknown-security-pitfalls-for-python/

https://github.com/pyenv/pyenv

0 条评论
某人
表情
可输入 255