ข้ามไปยังเนื้อหาหลัก

ความปลอดภัยเวบฮุก

เรียนรู้วิธีการรักษาความปลอดภัยของจุดสิ้นสุดเวบฮุกโดยใช้การตรวจสอบลายเซ็น ป้องกันการวนซ้ำ และใช้แนวทางปฏิบัติที่ดีด้านความปลอดภัย

ภาพรวม

ความปลอดภัยของเวบฮุกมีความสำคัญเพื่อให้แน่ใจว่าเหตุการณ์เวบฮุกที่ได้รับเป็นของแท้และส่งโดย Omise คำแนะนำนี้ครอบคลุม:

  • การตรวจสอบลายเซ็น HMAC-SHA256
  • การเปรียบเทียบสตริงที่ปลอดภัยจากการกำหนดเวลา
  • การจัดการคีย์ลับเวบฮุก
  • ขั้นตอนการหมุนเวียนลับ
  • ป้องกันการโจมตีเวลาวน

การตรวจสอบลายเซ็น

Omise ลงนามในคำขอเวบฮุกทั้งหมดด้วย HMAC-SHA256 โดยใช้คีย์ลับเวบฮุกของคุณ ลายเซ็นอยู่ในส่วนหัว X-Omise-Signature HTTP

วิธีการตรวจสอบลายเซ็น

  1. Omise สร้างแฮช HMAC-SHA256 ของเนื้อหาคำขอดิบโดยใช้คีย์ลับของคุณ
  2. ลายเซ็นถูกส่งในส่วนหัว X-Omise-Signature
  3. เซิร์ฟเวอร์ของคุณคำนวณแฮช HMAC-SHA256 เดียวกัน
  4. เปรียบเทียบลายเซ็นที่คำนวณกับลายเซ็นที่ได้รับ
  5. ประมวลผลเวบฮุกเฉพาะหากลายเซ็นตรงกัน

ตัวอย่างการใช้งาน

// Node.js - การตรวจสอบลายเซ็น
const crypto = require('crypto');

function verifySignature(rawBody, signature, secretKey) {
const expectedSignature = crypto
.createHmac('sha256', secretKey)
.update(rawBody)
.digest('hex');

// ใช้การเปรียบเทียบที่ปลอดภัยจากการกำหนดเวลา
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}

app.post('/webhooks/omise', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-omise-signature'];
const rawBody = req.body;

if (!signature || !verifySignature(rawBody, signature, process.env.OMISE_WEBHOOK_KEY)) {
return res.status(401).json({ error: 'Invalid signature' });
}

const event = JSON.parse(rawBody.toString());
res.status(200).json({ received: true });
processWebhook(event);
});
# Python - การตรวจสอบลายเซ็น
import hmac
import hashlib

def verify_signature(payload, signature, secret_key):
if isinstance(payload, str):
payload = payload.encode('utf-8')

expected_signature = hmac.new(
secret_key.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()

return hmac.compare_digest(signature, expected_signature)

@app.route('/webhooks/omise', methods=['POST'])
def handle_webhook():
payload = request.get_data()
signature = request.headers.get('X-Omise-Signature')

if not signature or not verify_signature(payload, signature, os.environ['OMISE_WEBHOOK_KEY']):
return jsonify({'error': 'Invalid signature'}), 401

event = request.get_json()
response = jsonify({'received': True})

process_webhook_async(event)
return response, 200
# Ruby - การตรวจสอบลายเซ็น
require 'openssl'

def verify_signature(payload, signature, secret_key)
expected_signature = OpenSSL::HMAC.hexdigest(
OpenSSL::Digest.new('sha256'),
secret_key,
payload
)

ActiveSupport::SecurityUtils.secure_compare(signature, expected_signature)
end

post '/webhooks/omise' do
payload = request.body.read
signature = request.env['HTTP_X_OMISE_SIGNATURE']

unless signature && verify_signature(payload, signature, ENV['OMISE_WEBHOOK_KEY'])
halt 401, { error: 'Invalid signature' }.to_json
end

event = JSON.parse(payload)
status 200
{ received: true }.to_json
end

การเปรียบเทียบที่ปลอดภัยจากการกำหนดเวลา

ใช้ฟังก์ชันการเปรียบเทียบที่ปลอดภัยจากการกำหนดเวลาเสมอเพื่อป้องกันการโจมตีการกำหนดเวลา การเปรียบเทียบสตริงปกติ (==, ===) สามารถรั่วไหลข้อมูลลายเซ็นผ่านความแตกต่างของการกำหนดเวลา

ฟังก์ชันการเปรียบเทียบที่ปลอดภัยจากการกำหนดเวลา

ภาษาฟังก์ชัน
Node.jscrypto.timingSafeEqual()
Pythonhmac.compare_digest()
RubyRack::Utils.secure_compare() หรือ ActiveSupport::SecurityUtils.secure_compare()
PHPhash_equals()
Gosubtle.ConstantTimeCompare()

การจัดการคีย์เวบฮุก

การเก็บคีย์อย่างปลอดภัย

แนวทางปฏิบัติที่ดี:

  • เก็บไว้ในตัวแปรสภาพแวดล้อม (ไม่ใช่ในโค้ด)
  • ใช้ระบบการจัดการความลับ (AWS Secrets Manager, HashiCorp Vault ฯลฯ)
  • ไม่ยืนยันคีย์ใน version control
  • ใช้คีย์ต่างๆ สำหรับโหมดทดสอบและการผลิต
  • จำกัดการเข้าถึงโดยใช้นโยบาย IAM
# ตัวแปรสภาพแวดล้อม
export OMISE_WEBHOOK_KEY="your_webhook_key_here"

# Docker
docker run -e OMISE_WEBHOOK_KEY="your_key" your-app

# Kubernetes
kubectl create secret generic omise-webhook \
--from-literal=key=your_webhook_key_here

การหมุนเวียนลับ

ทำให้หลุดเวลาปลอดภัยเป็นระยะ

ขั้นตอนการหมุนเวียน

  1. สร้างจุดสิ้นสุดเวบฮุกใหม่ ในแดชบอร์ด Omise
  2. อัปเดตแอปพลิเคชัน เพื่อตรวจสอบลายเซ็นด้วยทั้งคีย์เก่าและใหม่
  3. ตรวจสอบการจัดส่ง เพื่อให้แน่ใจว่าลำดับการทำงานอย่างถูกต้อง
  4. ลบคีย์เก่า หลังจากระยะเวลาการเปลี่ยนแปลง (เช่น 7 วัน)
  5. ปิดใช้งานจุดสิ้นสุดเก่า ในแดชบอร์ด

การตรวจสอบสิทธิ์หลายคีย์

ระหว่างการหมุนเวียน, ให้สนับสนุนทั้งคีย์เก่าและใหม่:

// Node.js - การตรวจสอบหลายคีย์
const OLD_KEY = process.env.OMISE_WEBHOOK_KEY_OLD;
const NEW_KEY = process.env.OMISE_WEBHOOK_KEY_NEW;

function verifySignatureWithRotation(rawBody, signature) {
// ลองคีย์ใหม่ก่อน
if (NEW_KEY) {
const newSig = crypto.createHmac('sha256', NEW_KEY)
.update(rawBody).digest('hex');

if (crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(newSig))) {
console.log('Verified with new key');
return true;
}
}

// ลองคีย์เก่า
if (OLD_KEY) {
const oldSig = crypto.createHmac('sha256', OLD_KEY)
.update(rawBody).digest('hex');

if (crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(oldSig))) {
console.log('Verified with old key (rotation in progress)');
return true;
}
}

return false;
}

ป้องกันการวนซ้ำ

ป้องกันผู้โจมตีจากการเล่นเวบฮุกที่จับได้

1. การติดตาม Event ID

เก็บ ID เหตุการณ์ที่ประมวลผลแล้วเพื่อปฏิเสธการซ้ำ:

// Node.js - การติดตาม Event ID ด้วย Redis
const redis = require('redis');
const client = redis.createClient();

async function isEventProcessed(eventId) {
const key = `webhook:processed:${eventId}`;
const exists = await client.exists(key);

if (exists) {
return true; // แล้วประมวลผล
}

// ทำเครื่องหมายเป็นประมวลผล (หมดอายุหลังจาก 7 วัน)
await client.setex(key, 7 * 24 * 3600, '1');
return false;
}

app.post('/webhooks/omise', async (req, res) => {
const event = JSON.parse(req.body);

if (await isEventProcessed(event.id)) {
console.log(`Duplicate event: ${event.id}`);
return res.status(200).json({ received: true });
}

res.status(200).json({ received: true });
processWebhook(event);
});

2. การตรวจสอบ Timestamp

ปฏิเสธเหตุการณ์ที่เก่าเกินไป:

// Node.js - การตรวจสอบ Timestamp
function isEventTimestampValid(createdAt, maxAgeMinutes = 5) {
const eventTime = new Date(createdAt);
const now = new Date();
const ageMinutes = (now - eventTime) / (1000 * 60);

return ageMinutes <= maxAgeMinutes;
}

app.post('/webhooks/omise', (req, res) => {
const event = JSON.parse(req.body);

if (!isEventTimestampValid(event.created_at)) {
console.log(`Event too old: ${event.id}`);
return res.status(400).json({ error: 'Event too old' });
}

res.status(200).json({ received: true });
processWebhook(event);
});

3. แนวทางรวม

ใช้ทั้งการติดตาม ID เหตุการณ์และการตรวจสอบ timestamp:

# Ruby - การป้องกันการวนซ้ำแบบรวม
require 'redis'

REDIS = Redis.new
MAX_EVENT_AGE_MINUTES = 5

def is_event_processed?(event_id)
key = "webhook:processed:#{event_id}"

return true if REDIS.exists?(key)

REDIS.setex(key, 7 * 24 * 3600, '1')
false
end

def is_event_timestamp_valid?(created_at)
event_time = Time.parse(created_at)
age_minutes = (Time.now - event_time) / 60

age_minutes <= MAX_EVENT_AGE_MINUTES
end

post '/webhooks/omise' do
event = JSON.parse(request.body.read)

# ตรวจสอบ timestamp
unless is_event_timestamp_valid?(event['created_at'])
logger.warn "Event too old: #{event['id']}"
halt 400, { error: 'Event too old' }.to_json
end

# ตรวจสอบสำหรับการซ้ำ
if is_event_processed?(event['id'])
logger.info "Duplicate event: #{event['id']}"
status 200
return { received: true }.to_json
end

status 200
{ received: true }.to_json
end

รายการตรวจสอบความปลอดภัย

  • ✓ ตรวจสอบลายเซ็น HMAC-SHA256 สำหรับเวบฮุกทั้งหมด
  • ✓ ใช้ฟังก์ชันการเปรียบเทียบที่ปลอดภัยจากการกำหนดเวลา
  • ✓ เก็บคีย์เวบฮุกในตัวแปรสภาพแวดล้อมหรือตัวจัดการลับ
  • ✓ ใช้ HTTPS พร้อมใบรับรอง SSL ที่ถูกต้อง
  • ✓ ใช้การติดตาม ID เหตุการณ์เพื่อป้องกันการวนซ้ำ
  • ✓ ตรวจสอบ timestamp เหตุการณ์เพื่อปฏิเสธเหตุการณ์เก่า
  • ✓ บันทึกความล้มเหลวในการตรวจสอบทั้งหมด
  • ✓ ตอบ 401 สำหรับลายเซ็นไม่ถูกต้อง
  • ✓ ใช้เนื้อหาคำขอดิบสำหรับการตรวจสอบลายเซ็น
  • ✓ หมุนเวียนคีย์เวบฮุกเป็นระยะ

ทรัพยากรที่เกี่ยวข้อง

ขั้นตอนถัดไป

  1. ใช้การตรวจสอบลายเซ็น ในตัวจัดการเวบฮุก
  2. ใช้ฟังก์ชันการเปรียบเทียบที่ปลอดภัยจากการกำหนดเวลา
  3. ตั้งค่าการติดตาม Event ID สำหรับป้องกันการวนซ้ำ
  4. ทดสอบการตรวจสอบลายเซ็น ในโหมดการทดสอบ
  5. ตั้งค่าการหมุนเวียนคีย เป็นระยะ
  6. ตรวจสอบเหตุการณ์ที่ซ้ำกัน ในการผลิต
  7. บันทึกความล้มเหลวในการตรวจสอบ เพื่อการตรวจสอบด้านความปลอดภัย

ต้องการการตั้งค่า ดูการตั้งค่าเวบฮุก

ต้องการการทดสอบ ดูการทดสอบเวบฮุก