安全小白入门(1)-----SQL注入

54 阅读2分钟

一、SQL 注入(SQL Injection)

1. 漏洞原理

SQL 注入是由于 用户输入未经过滤直接拼接到 SQL 语句中,导致攻击者可以构造恶意 SQL 代码,窃取、篡改或删除数据库数据。FastAPI 中若使用原生 SQL 语句且未做参数化,极易触发该漏洞。

2. 攻击场景(FastAPI 漏洞代码)

假设 FastAPI 接口通过用户 ID 查询用户信息,使用原生 SQL 且直接拼接用户输入:

from fastapi import FastAPI
import sqlite3

app = FastAPI()

# 漏洞接口:直接拼接用户输入到SQL语句
@app.get("/user/{user_id}")
def get_user(user_id: str):
    # 连接数据库(示例用SQLite)
    conn = sqlite3.connect("test.db")
    cursor = conn.cursor()
    
    # 危险!未过滤用户输入,直接拼接SQL
    sql = f"SELECT * FROM users WHERE id = {user_id};"
    cursor.execute(sql)  # 攻击者可构造恶意user_id注入SQL
    
    result = cursor.fetchone()
    conn.close()
    return {"user": result}

攻击步骤:

  1. 攻击者访问正常接口:/user/1,返回用户 ID=1 的信息。
  2. 构造恶意输入,窃取所有用户数据:访问 /user/1 OR 1=1,此时 SQL 语句变为:SELECT * FROM users WHERE id = 1 OR 1=1;由于 1=1 恒成立,会返回数据库中所有用户的信息。
  3. 进阶攻击:删除数据(若权限足够):访问 /user/1; DROP TABLE users;,SQL 语句变为:SELECT * FROM users WHERE id = 1; DROP TABLE users;导致 users 表被删除(破坏性攻击)。

3. 防护方案(修复代码 + 最佳实践)

核心原则:避免原生 SQL 拼接,使用参数化查询或 ORM 框架

修复方案 1:使用参数化查询(原生 SQL 安全写法)

@app.get("/user/{user_id}")
def get_user(user_id: str):
    conn = sqlite3.connect("test.db")
    cursor = conn.cursor()
    
    # 安全!使用参数化查询(?为占位符,避免拼接)
    sql = "SELECT * FROM users WHERE id = ?;"
    cursor.execute(sql, (user_id,))  # 第二个参数为元组,传入用户输入
    
    result = cursor.fetchone()
    conn.close()
    return {"user": result}

修复方案 2:使用 ORM 框架(推荐,如 SQLAlchemy)

ORM 框架会自动处理参数化,彻底杜绝 SQL 注入:

python

运行

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

# 1. 初始化数据库连接(SQLite)
engine = create_engine("sqlite:///test.db")
Base = declarative_base()
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# 2. 定义User模型
class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True, index=True)
    username = Column(String, unique=True, index=True)
    email = Column(String, unique=True, index=True)

# 3. 安全接口:使用ORM查询
@app.get("/user/{user_id}")
def get_user(user_id: int):  # 注意:这里用int类型,进一步限制输入
    db = SessionLocal()
    # ORM自动生成参数化SQL,无注入风险
    user = db.query(User).filter(User.id == user_id).first()
    db.close()
    return {"user": user}

额外防护:

  • 输入校验:用 FastAPI 的 Pydantic 模型限制输入类型(如user_id: int,拒绝字符串输入)。
  • 最小权限原则:数据库账号仅授予必要权限(如查询权限,无删除 / 修改权限)。