guest@websec: ~/websec/09-mass-assignment-and-authorization/th — zsh — 100×40 เขียนโดย SaKKo ENTH โค้ด:

guest@websec ~ % man 09-mass-assignment-and-authorization

09 · Mass Assignment และ Authorization

Broken access control คือความเสี่ยงอันดับ 1 ของเว็บ ทุก request ต้องตอบสองคำถาม: ผู้ใช้นี้ตั้งค่าฟิลด์ไหนได้ และทำอะไรกับเรคคอร์ดไหนได้?

9.1 Mass assignment

เขียน User.update(params[:user]) แล้วคัดลอกทั้งฟอร์มลงโมเดลมันง่าย แต่ผู้โจมตีเป็นคนเขียนฟอร์มนั้น เขาแค่เพิ่ม role: "admin" เข้าไป

เรื่องนี้เกิดกับ GitHub ในปี 2012 และเป็นเหตุผลที่ Rails มี strong parameters

sim: request อัปเดตโปรไฟล์แก้ JSON ที่เบราว์เซอร์ส่ง
controller uses:

เรคคอร์ดผู้ใช้หลังบันทึก

Strong params เปิดไว้แล้ว: ส่ง params ดิบจะ error คุณต้องระบุคีย์ที่อนุญาตด้วย permit หรือ params.expect ช่องโหว่คือ permit! (อนุญาตทุกอย่าง) และรายการ permit ที่มี :role Brakeman จับได้ทั้งสองแบบ
# Rails 8: expect = require + permit, and rejects wrongly shaped params with 400
def profile_params
  params.expect(user: [:name, :email])
end

# admin-only fields get their own permit list in the admin controller
def admin_user_params
  params.expect(user: [:name, :email, :role])
end

params.require(:user).permit!            # permits everything: never on user input
// JS stacks have the same bug: Object.assign(user, req.body) or ORM.update(req.body)
const { name, email } = req.body;              // pick fields explicitly
await User.update({ name, email }, { where: { id: req.user.id } });
await User.update(req.body, { where: { id: req.user.id } });   // mass assignment

9.2 Authorization: ระดับ object และระดับฟังก์ชัน

Authentication = คุณคือใคร Authorization = คุณทำอะไรได้ มีสองแบบที่พัง:

  • ผิดเรคคอร์ด (BOLA/IDOR): ดูใบแจ้งหนี้ได้ แต่ไม่ใช่ ใบนั้น
  • ผิดการกระทำ (BFLA): ผู้ใช้ทั่วไปเรียก POST /admin/refunds ได้ เพราะเราแค่ซ่อนปุ่ม

ระวังด้วย: การแก้ account_id ใน request การข้ามขั้นตอน และ API endpoint ที่ไม่ได้ตรวจเหมือนหน้า HTML

sim: ใครทำอะไรได้?เลือกบทบาทและการกระทำ
ไม่มีในตัว ให้ค้นผ่าน Current.user และใช้ Pundit หรือ Action Policy พร้อม verify_authorized เพื่อให้การลืมตรวจล้มเหลวแบบเห็นชัด
class ApplicationController < ActionController::Base
  include Pundit::Authorization
  after_action :verify_authorized, except: :index     # forgot authorize? → error
  after_action :verify_policy_scoped, only: :index
end

class RefundsController < ApplicationController
  def create
    order = policy_scope(Order).find(params[:order_id])  # object level (BOLA)
    authorize order, :refund?                            # function level (BFLA)
    order.refund!(amount: params.expect(:amount))
  end
end

class OrderPolicy < ApplicationPolicy
  def refund? = user.manager? && record.account_id == user.account_id
  class Scope < ApplicationPolicy::Scope
    def resolve = scope.where(account_id: user.account_id)   # tenant isolation
  end
end

9.3 ออกแบบ authorization ให้แข็งแรง

  • ปฏิเสธเป็นค่าเริ่มต้น ไม่มีกฎ ก็ไม่ได้สิทธิ์
  • ตรวจที่เซิร์ฟเวอร์ทุกครั้ง การซ่อนปุ่มไม่ใช่ความปลอดภัย
  • รวมกฎไว้ที่เดียว (policy object) ไม่ใช่ if admin? กระจายทั่ว
  • เอาบัญชีจาก session ห้ามเอาจาก params
  • ทดสอบ: ล็อกอินเป็นแต่ละบทบาทแล้วเช็กว่าได้ 403/404
  • ใช้ 404 แทน 403 เพื่อไม่ให้ไล่เดา ID ได้