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

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

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

ภาพรวม

ความปลอดภัยของเวบฮุกมีความสำคัญเพื่อให้แน่ใจว่าเหตุการณ์เวบฮุกที่ได้รับเป็นของแท้และส่งโดย 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 สำหรับลายเซ็นไม่ถูกต้อง
  • ✓ ใช้เนื้อหาคำขอดิบสำหรับการตรวจสอบลายเซ็น
  • ✓ หมุนเวียนคีย์เวบฮุกเป็นระยะ

คำถามที่พบบ่อย

ฉันจะรับคีย์ลายเซ็นเวบฮุกได้อย่างไร?

คีย์ลายเซ็นเวบฮุกจะแสดงในแดชบอร์ด Omise เมื่อคุณสร้างจุดสิ้นสุดเวบฮุกใหม่ คีย์จะแสดงเพียงครั้งเดียว ดังนั้นควรเก็บรักษาอย่างปลอดภัย หากสูญหาย ให้สร้างจุดสิ้นสุดเวบฮุกใหม่

ฉันสามารถใช้คีย์เวบฮุกเดียวกันสำหรับโหมดทดสอบและโหมดใช้งานจริงได้หรือไม่?

ไม่ได้ โหมดทดสอบและโหมดใช้งานจริงมีจุดสิ้นสุดเวบฮุกและคีย์ลายเซ็นแยกกัน กำหนดค่าจุดสิ้นสุดที่แตกต่างกันสำหรับแต่ละโหมด

จะเกิดอะไรขึ้นหากการตรวจสอบลายเซ็นล้มเหลว?

ส่งคืน HTTP 401 Unauthorized Omise จะลองส่งเวบฮุกอีกครั้งตามกำหนดการลองใหม่

ฉันควรตรวจสอบลายเซ็นสำหรับเวบฮุกโหมดทดสอบหรือไม่?

ใช่ ตรวจสอบลายเซ็นทั้งโหมดทดสอบและโหมดใช้งานจริงเสมอ เพื่อให้แน่ใจว่าโค้ดการตรวจสอบของคุณทำงานได้อย่างถูกต้องก่อนใช้งานจริง

ฉันควรหมุนเวียนคีย์เวบฮุกบ่อยแค่ไหน?

หมุนเวียนคีย์ทุก 6-12 เดือน หรือทันทีหากคุณสงสัยว่ามีการละเมิด ใช้การตรวจสอบคีย์คู่เพื่อหลีกเลี่ยงการหยุดทำงานระหว่างการหมุนเวียน

ฉันสามารถไวท์ลิสต์ที่อยู่ IP ของ Omise แทนการตรวจสอบลายเซ็นได้หรือไม่?

ไม่ได้ การไวท์ลิสต์ IP เพียงอย่างเดียวไม่เพียงพอ ตรวจสอบลายเซ็นเสมอเนื่องจากที่อยู่ IP สามารถถูกปลอมแปลงหรืออาจเปลี่ยนแปลงได้

ฉันควรทำอย่างไรหากตรวจพบการโจมตีแบบรีเพลย์?

บันทึกเหตุการณ์ ตอบกลับด้วย 200 OK เพื่อป้องกันการลองใหม่ และตรวจสอบรูปแบบ แจ้งเตือนทีมรักษาความปลอดภัยของคุณหากการโจมตียังคงดำเนินต่อไป

ฉันควรเก็บ ID เหตุการณ์ที่ประมวลผลแล้วนานเท่าไหร่?

เก็บ ID เหตุการณ์อย่างน้อย 7 วัน (ช่วงเวลาลองใหม่ของ Omise) การเก็บรักษานานขึ้น (30 วัน) ให้การป้องกันที่ดีกว่า

ฉันสามารถตรวจสอบลายเซ็นแบบซิงโครนัสได้หรือไม่?

ได้ การตรวจสอบลายเซ็นเร็วมาก (ไม่กี่มิลลิวินาที) ตรวจสอบแบบซิงโครนัสก่อนตอบกลับเวบฮุก

จะทำอย่างไรหากคีย์เวบฮุกของฉันถูกละเมิด?

สร้างจุดสิ้นสุดเวบฮุกใหม่พร้อมคีย์ใหม่ทันที อัปเดตแอปพลิเคชันของคุณ และปิดใช้งานจุดสิ้นสุดที่ถูกละเมิด

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

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

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

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

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