Lỗ hổng điều kiện chủng tộc là gì?
Điều kiện chạy đua xảy ra khi hai hoạt động phải xảy ra theo một thứ tự cụ thể, nhưng chúng có thể chạy theo thứ tự ngược lại.
Ví dụ: trong một ứng dụng đa luồng, hai luồng riêng biệt có thể truy cập vào một biến chung. Do đó, nếu một luồng thay đổi giá trị của biến, luồng kia vẫn có thể sử dụng phiên bản cũ hơn, bỏ qua giá trị mới nhất. Điều này sẽ gây ra kết quả không mong muốn.
Để hiểu rõ hơn về mô hình này, sẽ rất tốt nếu bạn kiểm tra chặt chẽ quá trình chuyển đổi quy trình của bộ vi xử lý.
Mục Lục
Cách một bộ xử lý chuyển đổi các quá trình
Hệ điều hành hiện đại có thể chạy nhiều hơn một tiến trình đồng thời, được gọi là đa nhiệm. Khi bạn xem xét quá trình này theo chu kỳ thực thi của CPU, bạn có thể thấy rằng đa nhiệm không thực sự tồn tại.
Thay vào đó, các bộ xử lý liên tục chuyển đổi giữa các quy trình để chạy chúng đồng thời hoặc ít nhất là hoạt động như thể chúng đang làm như vậy. CPU có thể làm gián đoạn một quá trình trước khi nó hoàn thành và tiếp tục một quá trình khác. Hệ điều hành kiểm soát việc quản lý các quá trình này.
Ví dụ, thuật toán Round Robin, một trong những thuật toán chuyển đổi đơn giản nhất, hoạt động như sau:
Nói chung, thuật toán này cho phép mỗi quá trình chạy trong một khoảng thời gian rất nhỏ, do hệ điều hành xác định. Ví dụ, đây có thể là khoảng thời gian hai micro giây.
CPU thực hiện lần lượt từng quá trình và thực hiện các lệnh sẽ chạy trong hai micro giây. Sau đó, nó tiếp tục đến quá trình tiếp theo, bất kể quá trình hiện tại đã kết thúc hay chưa. Do đó, từ quan điểm của người dùng cuối, nhiều hơn một quy trình dường như đang chạy đồng thời. Tuy nhiên, khi bạn nhìn vào hậu trường, CPU vẫn đang hoạt động theo thứ tự.
Nhân tiện, như sơ đồ trên cho thấy, thuật toán Round Robin thiếu bất kỳ khái niệm ưu tiên xử lý hoặc tối ưu hóa nào. Do đó, nó là một phương pháp khá thô sơ hiếm khi được sử dụng trong các hệ thống thực.
Bây giờ, để hiểu rõ hơn tất cả những điều này, hãy tưởng tượng rằng hai luồng đang chạy. Nếu các chủ đề truy cập một biến chung, một điều kiện chủng tộc có thể phát sinh.
Một ứng dụng web mẫu và điều kiện cuộc đua
Hãy xem ứng dụng Flask đơn giản bên dưới để phản ánh một ví dụ cụ thể về mọi thứ bạn đã đọc cho đến nay. Mục đích của ứng dụng này là quản lý các giao dịch tiền sẽ diễn ra trên web. Lưu phần sau vào một tệp có tên money.py:
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemyapp = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)
class Account(db.Model):
id = db.Column(db.Integer, primary_key = True)
amount = db.Column(db.String(80), unique = True)
def __init__(self, count):
self.amount = amount
def __repr__(self):
return '' % self.amount
@app.route("/")
def hi():
account = Account.query.get(1)
return "Total Money = {}".format(account.amount)
@app.route("/send/")
def send(amount):
account = Account.query.get(1)
if int(account.amount) < amount:
return "Insufficient balance. Reset money with /reset!)"
account.amount = int(account.amount) - amount
db.session.commit()
return "Amount sent = {}".format(amount)
@app.route("/reset")
def reset():
account = Account.query.get(1)
account.amount = 5000
db.session.commit()
return "Money reset."
if __name__ == "__main__":
app.secret_key = 'heLLoTHisIsSeCReTKey!'
app.run()
Để chạy mã này, bạn cần tạo một bản ghi trong bảng tài khoản và tiếp tục các giao dịch qua bản ghi này. Như bạn có thể thấy trong mã, đây là một môi trường thử nghiệm, vì vậy nó thực hiện các giao dịch đối với bản ghi đầu tiên trong bảng.
from money import db
db.create_all()
from money import Account
account = Account(5000)
db.session.add(account)
db.session.commit()
Bây giờ bạn đã tạo một tài khoản với số dư $ 5.000. Cuối cùng, chạy mã nguồn ở trên bằng lệnh sau, miễn là bạn đã cài đặt gói Flask và Flask-SQLAlchemy:
python money.py
Vì vậy, bạn có ứng dụng web Flask thực hiện một quá trình trích xuất đơn giản. Ứng dụng này có thể thực hiện các hoạt động sau với các liên kết yêu cầu GET. Vì Flask chạy trên cổng 5000 theo mặc định, nên địa chỉ bạn truy cập vào đó là 127.0.0.1:5000/. Ứng dụng cung cấp các điểm cuối sau:
- 127.0.0.1:5000/ hiển thị số dư hiện tại.
- 127.0.0.1:5000/send/{amount} trừ số tiền từ tài khoản.
- 127.0.0.1:5000/reset đặt lại tài khoản thành $ 5.000.
Bây giờ, ở giai đoạn này, bạn có thể kiểm tra xem lỗ hổng điều kiện chủng tộc xảy ra như thế nào.
Khả năng xảy ra lỗ hổng điều kiện cuộc đua
Ứng dụng web trên có thể có lỗ hổng về tình trạng chủng tộc.
Hãy tưởng tượng bạn có 5.000 đô la để bắt đầu và tạo hai yêu cầu HTTP khác nhau sẽ gửi 1 đô la. Đối với điều này, bạn có thể gửi hai yêu cầu HTTP khác nhau đến liên kết 127.0.0.1:5000/send/1. Giả sử rằng, ngay sau khi máy chủ web xử lý yêu cầu đầu tiên, CPU sẽ dừng quá trình này và xử lý yêu cầu thứ hai. Ví dụ: quá trình đầu tiên có thể tạm dừng sau khi chạy dòng mã sau:
account.amount = int(account.amount) - amount
Mã này đã tính tổng mới nhưng chưa lưu bản ghi trong cơ sở dữ liệu. Khi yêu cầu thứ hai bắt đầu, nó sẽ thực hiện cùng một phép tính, trừ đi $ 1 từ giá trị trong cơ sở dữ liệu— $ 5.000 — và lưu trữ kết quả. Khi quy trình đầu tiên tiếp tục, nó sẽ lưu trữ giá trị của chính nó— $ 4,999 — sẽ không phản ánh số dư tài khoản gần đây nhất.
Vì vậy, hai yêu cầu đã hoàn thành và mỗi yêu cầu phải trừ đi 1 đô la từ số dư tài khoản, dẫn đến số dư mới là 4.998 đô la. Tuy nhiên, tùy thuộc vào thứ tự mà máy chủ web xử lý chúng, số dư tài khoản cuối cùng có thể là $ 4,999.
Hãy tưởng tượng rằng bạn gửi 128 yêu cầu để thực hiện chuyển khoản 1 đô la đến hệ thống mục tiêu trong khung thời gian 5 giây. Kết quả của giao dịch này, bảng sao kê tài khoản dự kiến sẽ là $ 5,000 – $ 128 = $ 4,875. Tuy nhiên, do điều kiện cuộc đua, số dư cuối cùng có thể thay đổi trong khoảng từ $ 4,875 đến $ 4,999.
Lập trình viên là một trong những thành phần quan trọng nhất của bảo mật
Trong một dự án phần mềm, với tư cách là một lập trình viên, bạn có khá nhiều trách nhiệm. Ví dụ trên dành cho một ứng dụng chuyển tiền đơn giản. Hãy tưởng tượng làm việc trên một dự án phần mềm quản lý tài khoản ngân hàng hoặc phần phụ trợ của một trang thương mại điện tử lớn.
Bạn phải làm quen với các lỗ hổng như vậy để chương trình bạn đã viết để bảo vệ chúng khỏi lỗ hổng bảo mật. Điều này đòi hỏi một trách nhiệm mạnh mẽ.
Lỗ hổng về điều kiện chủng tộc chỉ là một trong số đó. Bất kể bạn sử dụng công nghệ nào, bạn cần phải đề phòng các lỗ hổng trong mã bạn viết. Một trong những kỹ năng quan trọng nhất mà bạn có thể có được với tư cách là một lập trình viên là làm quen với bảo mật phần mềm.