← กลับไปหน้าบทความ
ขั้นสูง (MarTech)

ใครก็ยิง POST มาที่ Endpoint รับ Webhook ได้ ถ้าไม่ตรวจลายเซ็นก่อน Conversion ปลอมก็เข้า CAPI ได้เหมือนของจริง

ทีมบรรณาธิการ linli07 ก.ย. 07:42อัปเดต 07 ก.ย. 07:42อ่าน 4 นาที
ใครก็ยิง POST มาที่ Endpoint รับ Webhook ได้ ถ้าไม่ตรวจลายเซ็นก่อน Conversion ปลอมก็เข้า CAPI ได้เหมือนของจริง
บทความนี้จัดทำเพื่อความรู้ ชื่อธุรกิจ ตัวเลข หรือบทสนทนาที่ไม่มีแหล่งอ้างอิงกำกับเป็นกรณีตัวอย่างเพื่ออธิบายแนวคิด ดูรายละเอียดที่ นโยบายกองบรรณาธิการ

สรุปสั้น ๆ

Endpoint รับ webhook ของ LINE ต้องเปิดเป็น URL สาธารณะเสมอเพื่อให้ LINE ยิงเข้ามาได้ แต่นั่นแปลว่าใครก็ยิง POST ปลอมเข้ามาได้เหมือนกัน ถ้าโค้ดไม่ตรวจลายเซ็น X-Line-Signature ก่อนประมวลผลทุกครั้ง payload ปลอมจะไหลเข้าสู่ pipeline เดียวกับของจริงและกลายเป็น conversion ปลอมที่ส่งเข้า CAPI ได้โดยไม่มีใครรู้ตัว

ทีมพัฒนาที่ผมเคยรีวิวโค้ดให้ เขียน endpoint รับ webhook จาก LINE ได้ดีมากในแง่ business logic แยก event ชนิดต่าง ๆ ประมวลผลถูกต้อง จนกระทั่งผมถามคำถามเดียวว่า ‘endpoint นี้ตรวจว่า payload ที่เข้ามาส่งมาจาก LINE จริงหรือเปล่าตรงไหน’ เขานิ่งไปพักหนึ่งก่อนตอบว่า ‘เอ่อ...เราเช็คแค่ว่า JSON structure ถูกต้องครับ’

นี่คือช่องโหว่ที่พบได้บ่อยกว่าที่คิด เพราะ URL ของ webhook endpoint ไม่ใช่ความลับ มันถูกตั้งค่าไว้ใน LINE Developers Console และถ้าใครเดา path ถูกหรือรู้ URL จากการดักจับ network traffic บนเว็บสาธารณะ ก็สามารถยิง HTTP POST ปลอมเข้ามาที่ endpoint เดียวกันได้ตรง ๆ โดยไม่ต้องผ่าน LINE เลย ถ้าโค้ดฝั่งเซิร์ฟเวอร์เชื่อ payload ทุกอันที่โครงสร้างถูกต้อง มันจะประมวลผล event ปลอมนั้นเหมือนเป็นของจริงทุกประการ

ผลที่ตามมาไม่ใช่แค่เรื่องความปลอดภัยเชิงทฤษฎี แต่กระทบตรงกับ conversion data ที่ไหลเข้า CAPI เพราะถ้ามีคนยิง payload ปลอมที่หน้าตาเหมือน order สำเร็จเข้ามาซ้ำ ๆ ระบบจะสร้าง event ปลอมส่งเข้า Meta หรือ Google ปนกับของจริงโดยที่ทีมการตลาดไม่มีทางรู้ได้เลยจนกว่าตัวเลขจะเพี้ยนไปมากจนสังเกตเห็น บทความนี้จะเจาะการตรวจลายเซ็นที่ต้องทำก่อนเชื่อ payload ใด ๆ ที่เข้ามาที่ endpoint

ทำไม endpoint เปิดสาธารณะถึงเป็นความเสี่ยงที่มองข้ามได้ง่าย

หลักการของ webhook คือ LINE ต้องยิง HTTP request มาหาเซิร์ฟเวอร์ของคุณได้จากภายนอก นั่นแปลว่า endpoint นี้ไม่สามารถซ่อนหลัง VPN หรือ authentication แบบ login ทั่วไปได้ มันต้องเปิดรับ request จากอินเทอร์เน็ตเสมอ ซึ่งเป็นข้อจำกัดโดยธรรมชาติของสถาปัตยกรรม webhook ทุกระบบไม่ใช่แค่ LINE

ปัญหาคือทีมพัฒนาจำนวนมากคิดว่า ‘ไม่มีใครรู้ URL นี้หรอก’ แล้วก็หยุดคิดเรื่องความปลอดภัยแค่นั้น แต่ในทางปฏิบัติ URL อาจรั่วได้หลายทาง เช่น หลุดไปอยู่ใน log ที่แชร์กันในทีม ถูกเก็บไว้ใน config ที่ commit ขึ้น repository สาธารณะโดยไม่ตั้งใจ หรือแม้แต่ถูกสแกนหาแบบสุ่มโดยบอทที่ไล่ยิง path ที่ชื่อคล้าย webhook ตามรูปแบบที่เฟรมเวิร์กยอดนิยมมักใช้ ความเสี่ยงนี้จึงไม่ใช่เรื่องสมมติ

LINE พิสูจน์ตัวตนของ payload ด้วย X-Line-Signature ยังไง

LINE แนบ header ชื่อ X-Line-Signature มากับทุก webhook request โดยค่าที่แนบมาคือผลลัพธ์จากการเข้ารหัส HMAC-SHA256 ของ request body ทั้งก้อน โดยใช้ channel secret ของแอปคุณเป็น key หน้าที่ของเซิร์ฟเวอร์คือคำนวณค่าเดียวกันนี้เองจาก body ที่ได้รับ แล้วเทียบกับค่าใน header ว่าตรงกันหรือไม่ ถ้าไม่ตรงต้องปฏิเสธ request ทันทีโดยไม่ต้องแตะ business logic ใด ๆ เลย

จุดสำคัญที่ทีมมักพลาดคือคำนวณ signature จาก body ที่ถูก parse เป็น object แล้ว (เช่น req.body ใน framework บางตัวที่ parse JSON อัตโนมัติก่อนถึงมือโค้ดของคุณ) ซึ่งจะให้ค่า signature ที่ไม่ตรงกับต้นฉบับเสมอ เพราะการแปลงกลับจาก object เป็น string อาจเรียงลำดับ key หรือจัดรูปแบบต่างจาก raw body ต้นฉบับที่ LINE ใช้คำนวณ ต้องเข้าถึง raw body แบบดิบก่อนที่ middleware ใด ๆ จะไปแตะมันเพื่อคำนวณ signature ให้ถูกต้อง

ลำดับการตรวจที่ถูกต้อง: verify ก่อน parse ไม่ใช่ parse ก่อน verify

  1. รับ raw request body แบบ string หรือ buffer ดิบ ๆ ก่อน ห้ามให้ middleware ใด ๆ แปลงเป็น JSON object ก่อนถึงขั้นตอนตรวจ signature
  2. ดึงค่า channel secret จาก environment variable ที่เก็บอย่างปลอดภัย ไม่ hardcode ไว้ในโค้ดโดยเด็ดขาด
  3. คำนวณ HMAC-SHA256 ของ raw body ด้วย channel secret แล้วเทียบกับค่าใน header X-Line-Signature โดยใช้ฟังก์ชันเปรียบเทียบแบบ constant-time เพื่อป้องกัน timing attack ไม่ใช่ใช้ operator เทียบสตริงธรรมดา
  4. ถ้าค่าไม่ตรงกัน ให้ตอบกลับด้วย status code ที่บ่งบอกว่าปฏิเสธ (เช่น 401) แล้วหยุดทันที ไม่เรียก business logic ต่อไม่ว่ากรณีใด
  5. เมื่อผ่านการตรวจแล้วเท่านั้น จึง parse body เป็น JSON แล้วส่งต่อเข้าสู่ pipeline ประมวลผล event ตามปกติ รวมถึงชั้นตรวจ idempotencyที่ควรอยู่ถัดจากขั้นตอนนี้

จุดพลาดที่พบบ่อยเมื่อทีมพยายามทำเรื่องนี้แต่ยังไม่สมบูรณ์

ความผิดพลาดผลที่ตามมาวิธีแก้
ตรวจ signature จาก body ที่ parse แล้วsignature ไม่ตรงเสมอ ทีมเลยปิดการตรวจไปเพราะคิดว่า LINE ส่งผิดอ่าน raw body ก่อน middleware parse JSON
เก็บ channel secret ไว้ในโค้ดหรือ config ที่ commit ขึ้น reposecret รั่วพร้อมกับ source code ทำให้คนนอกปลอม signature ได้จริงย้ายไป environment variable หรือ secret manager
log request body เต็มไว้เผื่อ debug โดยไม่กรองก่อนข้อมูลลูกค้าที่แนบมากับ event หลุดเข้า log แบบไม่ตั้งใจกรองฟิลด์อ่อนไหวก่อน log หรือ log เฉพาะ metadata

ตรวจ signature ผ่านแล้ว ยังต้องระวังอะไรอีกบ้าง

การตรวจ signature ป้องกันได้แค่เรื่อง ‘payload นี้มาจาก LINE จริงหรือเปล่า’ แต่ไม่ได้ป้องกันปัญหาชั้นอื่นที่ยังต้องออกแบบแยกต่างหาก เช่น replay attack ที่คนดักจับ request ที่ผ่าน signature ถูกต้องไปแล้ว แล้วยิงซ้ำอีกครั้งในภายหลัง ซึ่งจะผ่านการตรวจ signature ได้เหมือนเดิมเพราะเนื้อหาไม่เปลี่ยน จุดนี้ต้องอาศัยกลไกกันคำขอซ้ำที่ทำงานคู่กันไป ไม่ใช่พึ่งการตรวจ signature เพียงอย่างเดียว

อีกเรื่องที่ต้องแยกให้ชัดคือ การตรวจ signature ที่ชั้น webhook เป็นคนละชั้นกับความปลอดภัยของ credential ที่ใช้ยิงข้อมูลออกไปยัง Meta, Google หรือ TikTok CAPI ต่อ token หรือ access key ของแพลตฟอร์มโฆษณาเหล่านั้นต้องได้รับการดูแลแยกต่างหากด้วยมาตรฐานของตัวเอง ไม่ใช่คิดว่าตรวจ signature ฝั่งขาเข้าแล้วปลอดภัยครบวงจร

ทดสอบว่าการตรวจ signature ทำงานจริง ไม่ใช่แค่เขียนโค้ดไว้เฉย ๆ

วิธีทดสอบที่ตรงไปตรงมาคือ ลองยิง payload ปลอมที่มี signature ผิดเข้า endpoint โดยตั้งใจ แล้วดูว่าระบบปฏิเสธจริงหรือเปล่า หลายทีมเขียนโค้ดตรวจ signature ไว้ถูกต้องตามทฤษฎี แต่ลืมต่อผลการตรวจเข้ากับ flow การปฏิเสธจริง กลายเป็นว่าตรวจแล้วแต่ไม่ได้ทำอะไรกับผลตรวจนั้น

ควรมี test case ที่ครอบคลุมทั้งกรณี signature ถูกต้อง (ต้องผ่าน) และ signature ผิด (ต้องถูกปฏิเสธ) รวมถึงกรณี header หายไปเลย (ต้องถูกปฏิเสธเช่นกัน ไม่ใช่ default เป็นผ่าน) การมี test suite แบบนี้ติดอยู่ในขั้นตอนทดสอบ CAPIที่ทำก่อนขึ้น production จะช่วยจับความผิดพลาดที่มองข้ามได้ก่อนที่จะกลายเป็นช่องโหว่จริง

ถ้าต้องหมุนเวียน channel secret ใหม่ การตรวจ signature จะพังไหม

อีกสถานการณ์ที่ทีมมักไม่เคยคิดล่วงหน้าคือ วันหนึ่งที่ต้องเปลี่ยน channel secret ใหม่ ไม่ว่าจะเป็นเพราะนโยบายความปลอดภัยภายในกำหนดให้หมุนเวียน credential เป็นระยะ หรือเพราะสงสัยว่า secret เดิมอาจรั่วไหลไปแล้ว การเปลี่ยนค่านี้มีผลกระทบตรงกับการตรวจ signature ทันที เพราะถ้าเปลี่ยนค่าใน LINE Developers Console แล้วแต่โค้ดฝั่งเซิร์ฟเวอร์ยังใช้ค่าเก่าอยู่ signature ที่คำนวณได้จะไม่ตรงกับที่ LINE ส่งมาเลยสักครั้ง กลายเป็นว่า webhook ทั้งหมดถูกปฏิเสธไปหมดโดยไม่มีใครรู้ตัวจนกว่าจะสังเกตเห็นว่า event หยุดไหลเข้าระบบ

แนวทางที่ปลอดภัยกว่าคือไม่เปลี่ยนค่าทั้งสองฝั่งพร้อมกันในจังหวะเดียว ควรอัปเดตค่า channel secret ในฝั่งเซิร์ฟเวอร์ให้พร้อมใช้งานก่อน แล้วค่อยยืนยันว่าโค้ดอ่านค่าจาก environment variable ใหม่ถูกต้องผ่านการทดสอบใน sandbox ก่อนจะไปเปลี่ยนค่าจริงใน LINE Developers Console เพื่อลดช่วงเวลาที่ทั้งสองฝั่งไม่ตรงกัน และควรมี alert ที่แจ้งเตือนทันทีถ้าอัตราการปฏิเสธ signature พุ่งสูงผิดปกติ เพราะนั่นคือสัญญาณที่ชัดเจนที่สุดว่าเกิดความไม่ตรงกันของ secret ระหว่างสองฝั่งขึ้นแล้ว

สรุป

endpoint ที่รับ webhook ต้องเปิดสาธารณะเป็นธรรมชาติของสถาปัตยกรรมนี้ แต่นั่นไม่ได้แปลว่าต้องเชื่อทุก request ที่เข้ามาโดยไม่ตรวจสอบ การตรวจ X-Line-Signature ก่อนประมวลผลทุกครั้งคือด่านแรกที่ราคาถูกที่สุดในการป้องกัน event ปลอมไม่ให้เข้าสู่ pipeline ที่ไหลต่อไปถึง CAPI

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

  • endpoint รับ webhook เปิดสาธารณะเสมอ ต้องตรวจ X-Line-Signature ก่อนเชื่อ payload ทุกครั้ง
  • ต้องคำนวณ signature จาก raw body ก่อนที่ middleware จะ parse เป็น JSON ไม่งั้นค่าจะไม่ตรงเสมอ
  • การตรวจ signature เป็นด่านแรก ไม่ใช่ด่านสุดท้าย ยังต้องมีกลไกกันคำขอซ้ำและดูแล credential แยกอีกชั้น

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

ถ้าไม่ตรวจ signature เลย จะมีผลกระทบทันทีไหม หรือแค่เป็นความเสี่ยงในทางทฤษฎี

ในทางปฏิบัติหลายระบบไม่เคยถูกโจมตีจริงและทำงานได้ตามปกติ แต่นั่นไม่ได้แปลว่าไม่มีความเสี่ยง เพราะช่องโหว่นี้เปิดโอกาสให้ใครก็ตามที่รู้ URL endpoint ปลอม event ได้ ความเสี่ยงจึงมีอยู่จริงแม้จะยังไม่เกิดเหตุการณ์ ควรปิดช่องโหว่นี้ตั้งแต่ตอนออกแบบระบบมากกว่ารอให้เกิดปัญหาก่อน

channel secret กับ channel access token ต่างกันยังไง อันไหนใช้ตรวจ signature

channel secret ใช้สำหรับคำนวณและตรวจ X-Line-Signature ส่วน channel access token ใช้สำหรับเรียก LINE Messaging API เพื่อส่งข้อความหรือดึงข้อมูลกลับ ทั้งสองค่าต้องเก็บอย่างปลอดภัยเหมือนกันแต่ใช้งานคนละหน้าที่ ไม่ควรสลับใช้งานผิดที่

ถ้า signature ตรวจผ่านแล้ว แปลว่า event นั้นเชื่อถือได้ 100% เลยไหม

แปลว่า payload นั้นมาจาก LINE จริงตามที่ signature ยืนยันได้ แต่ยังไม่ได้แปลว่าปลอดภัยจากทุกความเสี่ยง เช่น replay attack ที่ต้องอาศัยกลไกกันคำขอซ้ำแยกต่างหาก การตรวจ signature เป็นด่านแรกที่จำเป็น ไม่ใช่ด่านสุดท้ายที่เพียงพอ

ระบบเล็กที่มี traffic น้อยมาก ยังจำเป็นต้องตรวจ signature ไหม

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

จะรู้ได้ยังไงว่าเคยมีคนพยายามยิง payload ปลอมเข้าระบบมาก่อน

ควรมี metric หรือ log แยกที่นับจำนวนครั้งที่ signature ตรวจไม่ผ่าน ถ้าตัวเลขนี้ขยับขึ้นผิดปกติในบางช่วงเวลา นั่นคือสัญญาณว่ามีความพยายามยิง payload ที่ไม่ผ่านการตรวจเข้ามา ซึ่งควรตรวจสอบเพิ่มเติมว่าเกิดจากอะไร

การตรวจ signature ทำให้ response ช้าลงจนกระทบ latency budget ของ webhook ไหม

การคำนวณ HMAC-SHA256 ใช้เวลาน้อยมากระดับมิลลิวินาที ไม่ใช่คอขวดของ latency ในระบบส่วนใหญ่ ปัญหา latency ที่แท้จริงมักมาจากขั้นตอนอื่นอย่างการเขียนฐานข้อมูลหรือเรียก API ภายนอก ซึ่งเป็นคนละประเด็นกับการตรวจ signature โดยสิ้นเชิง

ลองตรวจด้วยตัวเอง

LINE Webhook Signature Validator

ทดสอบ signature กับ raw body และ channel secret ของคุณได้ในเบราว์เซอร์ — secret ไม่ถูกส่งขึ้นเซิร์ฟเวอร์

ตรวจ signature ฟรี

ใช้ฟรี ไม่ต้องสมัคร ไม่เก็บข้อมูลที่คุณกรอก

วัด Conversion LINE ทุกแบรนด์ รวมศูนย์ที่เดียว

องค์กรที่มีหลายแบรนด์ หลายสาขา หลาย LINE OA — linli รวม Tracking และส่ง Conversion กลับทุกแพลตฟอร์ม พร้อม Custom Limit เริ่มฟรี 14 วัน

ติดต่อทีม Sales

บทความที่เกี่ยวข้อง

ออเดอร์เดียวต้องส่งเข้า Meta, Google และ TikTok พร้อมกัน แต่ event_id เดียวใช้ซ้ำสามที่ไม่ได้ ออกแบบ Fan-out Pipeline ให้ไม่ชนกันเอง

ออเดอร์เดียวต้องส่งเข้า Meta, Google และ TikTok พร้อมกัน แต่ event_id เดียวใช้ซ้ำสามที่ไม่ได้ ออกแบบ Fan-out Pipeline ให้ไม่ชนกันเอง

ธุรกิจที่ยิงแอดหลายแพลตฟอร์มพร้อมกันต้องส่ง conversion เดียวกันออกไปหลายทาง แต่ถ้าใช้ event_id เดียวกันหมดหรือยิงจากจุดเดียวแบบเรียงลำดับ ความเสี่ยงเรื่องความล่าช้าและ error จะเพิ่มขึ้นตามจำนวนปลายทาง
ก่อนแคมเปญ 11.11 ทีมรู้อยู่แล้วว่าคนจะทักพร้อมกันเป็นพัน แต่ไม่เคยทดสอบระบบรับ Traffic ขนาดนั้นมาก่อนสักครั้ง วาง Load Test ล่วงหน้าก่อนวันจริง

ก่อนแคมเปญ 11.11 ทีมรู้อยู่แล้วว่าคนจะทักพร้อมกันเป็นพัน แต่ไม่เคยทดสอบระบบรับ Traffic ขนาดนั้นมาก่อนสักครั้ง วาง Load Test ล่วงหน้าก่อนวันจริง

ต่างจากแคมเปญที่พีคแบบไม่คาดคิด แคมเปญใหญ่อย่าง 11.11 หรือสิ้นปีรู้ล่วงหน้าอยู่แล้วว่าจะมี traffic สูง บทความนี้เจาะการวาง load test เชิงรุกก่อนวันจริง แทนการรอให้ระบบพังแล้วค่อยแก้ตามหลัง
ดีล B2B ปิดจริงหลังผ่าน 4 ขั้นตอนในสองเดือน แต่ Event เดียวตอนปิดดีลไม่พอให้ Bidding เรียนรู้ทัน ออกแบบ Progressive Value Update ระหว่างทาง

ดีล B2B ปิดจริงหลังผ่าน 4 ขั้นตอนในสองเดือน แต่ Event เดียวตอนปิดดีลไม่พอให้ Bidding เรียนรู้ทัน ออกแบบ Progressive Value Update ระหว่างทาง

ธุรกิจ B2B ที่วงจรขายยาวหลายสัปดาห์หรือหลายเดือน ส่ง conversion event เดียวตอนปิดดีลไม่พอให้ระบบ bidding เรียนรู้ทันเวลา บทความนี้เจาะการออกแบบ event ทยอยอัปเดตมูลค่าตามความคืบหน้าของดีล